import "!babel-loader!mdx-loader!foo.mdx" not working with react-scripts#5.0.0 - reactjs

Trying to upgrade an existing React project from react-scripts#4 to #5.0.0 fails for importing transpiled MDX sources.
/* eslint import/no-webpack-loader-syntax: off */
import AboutMDX from "!babel-loader!mdx-loader!./About.mdx"
AboutMDX does not receive an MDXComponent but instead now as of react-scripts 5 end up with a string which is the path name of the transpiled javascript source code file. How can I fix this change in behavior to correctly import the MDXComponent?

This has been an od(d)ysee because the whole MDX2+CRA5+remark/rehype ecosystem is prone to breakage in my experience. While MDX documents to use CRACO7 with CRA5, the MDX project when kindly asked points fingers to CRACO and wasn't helpful to me in getting me over ES modules and CSJ hurdles in order to finally get the pieces to work. While the following now works for me (at the moment) I don't know how robust this set up actually is.
upgrade to CRA 5
install CRACO 5
make sure to call the craco command instead of react in your package.json scripts.
make sure to clean up your (stale) dependencies.
add these dependencies and dev dependencies:
"#mdx-js/loader": "^2.2.1",
"#mdx-js/mdx": "^2.2.1",
"#mdx-js/react": "^2.2.1",
"#types/mdx": "^2.0.3",
"#craco/craco": "^7.0.0",
"#craco/types": "^7.0.0",
if in the past you had a declare module '*.mdx {...}' in a src/index.d.ts then remove this now completely, as it would conflict with what comes with the MDXv2 loader.
remove !babel-loader!mdx-loader! from all your *.mdx imports. Do not use !#mdx-js/loader! etc. either, as the webpack configuration below will take care of the preprocessing.
create a craco.config.js as follows; this is a more elaborate configuration that shows how to actually pull in ES modules with CRACO 5 still not supporting ESM in their configuration, but requiring to go through the dynamic import with delayed configuration setup hurdles:
const { addAfterLoader, loaderByName } = require('#craco/craco')
module.exports = async (env) => {
const remarkGfm = (await import('remark-gfm')).default
const remarkImages = (await import('remark-images')).default
const remarkTextr = (await import('remark-textr')).default
const rehypeSlug = (await import('rehype-slug')).default
const textrTypoApos = (await import('typographic-apostrophes')).default
const textrTypoQuotes = (await import('typographic-quotes')).default
const textrTypoPossPluralsApos = (await import('typographic-apostrophes-for-possessive-plurals')).default
const textrTypoEllipses = (await import('typographic-ellipses')).default
const textrTypoEmDashes = (await import('typographic-em-dashes')).default
const textrTypoEnDashes = (await import('typographic-en-dashes')).default
return {
webpack: {
configure: (webpackConfig) => {
addAfterLoader(webpackConfig, loaderByName('babel-loader'), {
test: /\.(md|mdx)$/,
loader: require.resolve('#mdx-js/loader'),
/** #type {import('#mdx-js/loader').Options} */
options: {
remarkPlugins: [
remarkGfm,
remarkImages,
[remarkTextr, {
plugins: [
textrTypoApos,
textrTypoQuotes,
textrTypoPossPluralsApos,
textrTypoEllipses,
// textrTypoEmDashes,
textrTypoEnDashes,
],
options: {
locale: 'en-us'
}
}],
],
rehypePlugins: [
rehypeSlug,
],
}
})
return webpackConfig
}
}
}
}

Related

react scripts build generate new hash even if the code not changes

I build react app without create-react-app (without eject).
I want to generate new hash every build if the code not change (because cache issue).
I installed react-app-rewired for using config overloads and change package.json to
"build": "react-app-rewired build",
in config-overrides.js I'm trying to create new hash for each build (minified, css, js,styled and etc) but not sure I do it in right way
require('dotenv').config();
var uniqid = require('uniqid');
const FileManagerPlugin = require('filemanager-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
webpack: function (config, env) {
console.log('outputconfig before', config.output);
const buildHash = uniqid();
config.output.filename = `static/js/[name].${buildHash}.js`;
config.output.chunkFilename = `static/js/[name].${buildHash}.chunk.js`;
console.log('outputs config', config.output);
return config;
},
};
when I deploy it to production it looks like the hash build is the same if the code has not changes.. not sure if I configure the config-overloads.js right, maybe I need to add webpack or something not sure.
I want every build to generate new unique name to js, css and html files.
If the input is the same, if you did not change anything in the codebase, webpack will ALWAYS produce the same hash. This is the main property of a hash function. So you have to somehow always change the codebase. To achieve this you can write to a js file and import it in app.js so webpack will see that file's input has changed. In your webpack.config.js
const crypto = require("crypto");
const fs = require("fs");
const content = crypto.randomBytes(8).toString("hex");
const value = JSON.stringify(content);
// we have to write a valid javascript
fs.writeFile("src/test.js", `export const a=${value}`, (err) => {
if (err) {
console.error(err);
}
});
this test.js has no effect on your code. So you could safely import in app.js
import "./test.js";
If you dont import it will not work. Because webpack will read only imported code.
in webpack.config.js i defined the filename like this
output: {
filename: "[name].[hash].js",
path: path.join(__dirname, "dist"),
publicPath: "/",
},
every time I run npm run build it creates a different hash. Proof of work:

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

Monaco editor with nextjs

Getting this error when using monaco editor with next js.
Have anyone resolved this?
Failed to compile
./node_modules/monaco-editor/esm/vs/base/browser/ui/actionbar/actionbar.css
Global CSS cannot be imported from within node_modules.
Read more: https://err.sh/next.js/css-npm
Location: node_modules/monaco-editor/esm/vs/base/browser/ui/actionbar/actionbar.js```
Add this webpack config fixed it for me
const withCSS = require('#zeit/next-css');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = withCSS({
webpack(config, options) {
config.plugins.push(new MonacoWebpackPlugin());
return config;
},
cssLoaderOptions: { url: false }
})
#monaco-editor/react third party helper
I'm not sure why it works, I would rather avoid third party stuff, but this is he first thing I got to work, they just package it so nicely, so here it is:
https://www.npmjs.com/package/#monaco-editor/react
https://github.com/suren-atoyan/monaco-react
you can get rid of MonacoWebpackPlugin when using that.
Key dependencies I tested with:
"dependencies": {
"#monaco-editor/react": "4.2.1",
"next": "11.0.1",
"monaco-editor": "0.26.1",
Then usage is as described on README. They expose a Editor component helper, but you can also get a monaco instance if you want.
Related:
https://dev.to/swyx/how-to-add-monaco-editor-to-a-next-js-app-ha3
https://github.com/react-monaco-editor/react-monaco-editor/issues/271
https://github.com/resourcesco/snippets/blob/master/docs/monaco-editor-with-next.md
https://github.com/react-monaco-editor/react-monaco-editor/issues/334
https://github.com/ritz078/transform/issues/282

React Native 0.60.3 babel-plugin-transform-remove-console not working

I am trying to remove console.log outputs from my react-native application's output, but when I run
ENVFILE=.env.production react-native run-android --variant=release
and
adb logcat
I still see my app's data being logged to the console.
I used the following documentation: https://facebook.github.io/react-native/docs/performance.html#using-consolelog-statements.
Here is my .babelrc file:
{
"presets": ["react-native"],
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
}
What am I missing ?
Im on react-native 0.60.3
and using "babel-plugin-transform-remove-console": "^6.9.4",
I have "#babel/core": "^7.5.5" and "react-native": "^0.60.5"
The approach descibed in React Native Documentation was not working for me.After many try and error and exploring issues on GitHub I got it working :
In babel.config.js add this -
module.exports = api => {
const babelEnv = api.env();
const plugins = [];
//change to 'production' to check if this is working in 'development' mode
if (babelEnv !== 'development') {
plugins.push(['transform-remove-console', {exclude: ['error', 'warn']}]);
}
return {
presets: ['module:metro-react-native-babel-preset'],
plugins,
};
};
To see changes run using npm start -- --reset-cache
More Info at
https://github.com/babel/minify/issues/934
https://github.com/babel/minify/issues/950#issuecomment-539590159
Using babel.config.js instead of .babelrc, it seems that process.env.BABEL_ENV is used to determine whether to include configs listed under env.production. However, process.env.BABEL_ENV is set to undefined during build.
To get around this, I'm returning a different object depending on if process.env.BABEL_ENV OR process.env.NODE_ENV indicate production build.
YMMV.
module.exports = function(api) {
api.cache(true); // necessary
if (process.env.NODE_ENV === 'production' || process.env.BABEL_ENV === 'production') {
return {
"presets": ["module:metro-react-native-babel-preset"],
"plugins": ["react-native-paper/babel", "transform-remove-console"]
}
} else {
return {
"presets": ["module:metro-react-native-babel-preset"],
}
}
}
install babel-plugin-transform-remove-console
yarn add babel-plugin-transform-remove-console -D
then add follow code in babel.config.js like this
module.exports = function (api) {
const babelEnv = api.env();
api.cache(true);
const plugins = [
[];
if (babelEnv === 'production') {
plugins.push(['transform-remove-console', {exclude: ['error', 'warn']}]);
}
return {
presets: ['babel-preset-expo'],
plugins,
};
};
then run this command
yarn start --reset-cache
NOTE:
this will remove console.log in the production build. if you wanna a test in development you can pass development instead of production

Setting up babel-plugin-styled-components + Typescript + create-react-app

We are currently trying to integrate the babel-plugin-styled-components into our typescript and create-react-app based setup for a better debugging experience and we are having difficulties doing so.
We are reluctant to eject the app, which is why we are trying to set it up using react-app-rewired and we also managed to get our typescript code to compile using react-app-rewire-typescript along with react-app-rewire-styled-components.
For some reason however, the displayName is not applied, which makes me think the babel plugin is not applied.
We are using "start": "react-app-rewired start" as our dev server script and the config-overrides.js looks like this:
const rewireTypescript = require('react-app-rewire-typescript');
const rewireStyledComponents = require('react-app-rewire-styled-components');
module.exports = function override(config, env) {
return rewireTypescript(rewireStyledComponents(config, env), env);
}
I have no idea what we are missing. Swapping the encapsulation of the rewire... functions also didn't help.
Does anyone here have experience with that or can point me in the right direction?
If you are using webpack, I found a solution that did not require using babel.
https://github.com/Igorbek/typescript-plugin-styled-components
To use it, you add a custom transform to your typescript loader with webpack:
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader',
options: {
getCustomTransformers: () => ({ before: [styledComponentsTransformer] })
}
}
This solves the displayName not showing when using styled-components and typescript.
I solved the issue by add something like this in config-overrides.js using react-app-rewired:
const createStyledComponentsTransformer = require('typescript-plugin-styled-components').default;
const styledComponentsTransformer = createStyledComponentsTransformer();
module.exports = function override(config, env) {
const rule = config.module.rules.filter(l => l.oneOf)[0];
const tsLoader = rule.oneOf.filter(l => String(l.test) === String(/\.(ts|tsx)$/))[0];
tsLoader.use[0].options.getCustomTransformers = () => ({
before: [styledComponentsTransformer]
});
return config;
}

Resources