PrismJS with React : using babel-plugin-prismjs - reactjs

I recently decided to use PrismJS with React and to avoid import repetitions I managed to use this babel-plugin-prismjs package in order to load plugins, languages and so on.
As indicated in the plugin documentation, I've created a .babelrc file located in my root folder :
{
"plugins": [
["prismjs", {
"languages": [
"applescript",
"css",
"javascript",
"markup",
"scss"
],
"plugins": ["line-numbers"],
"theme": "twilight",
"css": true
}]
]
But I found that this file seemed to be ignored, as nothing is loaded and when console-logging my imported Prism object I'm only seeing syntax highlighting for the default languages.
The file where I want to get syntax highlighting have an import Prism from 'prismjs' statement and Prism.highlightAll()
So yes I can keep importing manually plugins, theme and languages in each of my files but I would want to find the reason of such an issue.
Thank you !

You're halfway there. You still need to import prismjs somewhere, usually in your app.js file, and then call Prism.highlightAll() in the appropriate file of the page you want syntax highlighting on.
My process was as follows:
Setup my .babelrc.js file:
const env = require('./env-config')
module.exports = {
presets: ['next/babel'],
plugins: [
[
'transform-define',
env,
],
[
'prismjs', {
'languages': ['javascript', 'css', 'html', 'jsx'],
'plugins': ['line-numbers', 'show-language', 'copy-to-clipboard'],
'theme': 'tomorrow',
'css': true
},
]
],
}
Import prismjs into my _app.js file (since I'm using Next.js, but with React, you would import this into our app.js file:
// ...
import 'prismjs'
// ...
Use the prismjs API to call the .highlightAll() method on your desired page(s):
function usePrismHighlightAll() {
useEffect(() => {
Prism.highlightAll()
}, [])
}
export default function Page () {
usePrismHighlightAll()
// ...
}
As a sidenote,
could also move the usePrismHighlightAll() hook somewhere into your app.js file so that you could enable syntax highlighting in all your pages if that would save you from recalling the hook in several locations.
However, I tried doing this, and unfortunately, it did not work in Next.js:
/* DO NOT USE THIS CODE NEXT.JS AS IT DOES NOT WORK */
import 'prismjs'
// ...
function usePrismHighlightAll() {
useEffect(() => {
Prism.highlightAll()
}, [])
}
export default function App ({ Component, pageProps }) {
usePrismHighlightAll()
return (
<>
<Layout>
<Header />
<Component {...pageProps} />
</Layout>
</>
)
}
/* DO NOT USE THIS CODE NEXT.JS AS IT DOES NOT WORK */
So, stick with the process I outlined in steps 1-3.
Tthat worked for me to enable syntax highlighting on the first load of the page, let me know if this works for you.

Related

Is there a way to oraginize imports in vs code without removing 'react import'

I am trying to organize imports on saving a file. So I updated vs code settings to always organize imports when saving a file.
But it also removes import React from 'react'.
So react gives me this error 'React' must be in scope when using JSX.
For eg,
import React from 'react'
const Temp = () => {
return (
<div>Temp</div>
)
}
export default Temp
organizes to
const Temp = () => {
return <div>Temp</div>;
};
export default Temp;
This is my react version - "react": "^16.13.1".
Have you tried using a babel.config.js file?
module.exports = {
presets: [
[
'#babel/preset-env',
{
modules: false,
},
],
['#babel/preset-react', { runtime: 'automatic' }],
],
};
I have a project that uses this and it works pretty fine.
Refer the docs for configuration.

React + Rollup - 'r is not defined'

Final edit: Thanks everyone for your help, however ultimately it was easier for me to transition to Webpack and Storybook. I'm leaving my original question untouched just in case it helps anyone in the future. Also, if anyone stumbles upon any issues configuring these (like I did), the link to the GitHub repo is below.
I'm creating a small lib using React and Rollup and trying to test locally with a CRA-powered project, however I'm facing this issue when importing a component from my library. I don't know if the problem is in my configuration or if this is a bug.
Uncaught ReferenceError: r is not defined
at Object.../dist/bundle.js (index.jsx:44)
Imported "Message" component where the error is happening
import React, { useEffect, useState } from 'react';
import { string, number, arrayOf } from 'prop-types';
import Container from './Container';
function Message({
text,
type,
timeout,
classes,
}) {
const [show, setShow] = useState(false);
useEffect(() => {
if (text && type) {
setShow(true);
setTimeout(() => setShow(false), timeout);
}
}, [text, type]);
const date = new Date();
return (
<Container
id={`message-${date}`}
key={`message-${date}`}
className={`${type}${classes?.map((className) => ` ${className}`)}`}
>
{
show
? (
<p>{text}</p>
) : ''
}
</Container>
);
}
// The source map points to this line when the error happens, but it still happens if I remove it and don't use prop-types, instead pointing to the closing bracket of the 'Message' function
Message.defaultProps = {
timeout: 3000,
classes: [],
};
Message.propTypes = {
text: string.isRequired,
type: string.isRequired,
timeout: number,
classes: arrayOf(string),
};
export default Message;
Test component where it's being used:
import React from 'react';
import { Message } from 'pure-ui';
import { getRandomArrayElement } from 'formatadores';
const types = [
'warning',
'error',
'success',
];
const texts = [
'This is a test',
'I will randomly display a message every so often, so stay sharp',
'Yet another test message',
];
const timeouts = [
5000,
3000,
1000,
];
function App() {
return (
<div>
<h1>Running...</h1>
<Message
type={getRandomArrayElement(types)}
text={getRandomArrayElement(texts)}
timeout={getRandomArrayElement(timeouts)}
/>
</div>
);
}
export default App;
rollup config file:
import babel from '#rollup/plugin-babel';
import resolve from '#rollup/plugin-node-resolve';
import commonjs from '#rollup/plugin-commonjs';
import external from 'rollup-plugin-peer-deps-external';
import React from 'react';
import propTypes from 'prop-types';
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
export default [
{
input: 'src/index.js',
watch: true,
output: {
file: 'dist/bundle.js',
format: 'iife',
sourcemap: true,
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'prop-types': 'PropTypes',
},
},
plugins: [
external(),
babel({
exclude: 'node_modules/**',
presets: [
'#babel/preset-env',
['#babel/preset-react', { runtime: 'automatic' }],
],
}),
resolve({ extensions }),
commonjs({
namedExports: {
react: Object.keys(React),
'react/jsx-runtime': ['jsx', 'jsxs', 'Fragment'],
'react/jsx-dev-runtime': ['jsx', 'jsxs', 'jsxDEV'],
'prop-types': Object.keys(propTypes),
},
}),
],
external: [
'react',
'react-dom',
'prop-types',
],
},
];
I tried changing the namedExports (and also removing them), linking React from the lib to use the same version from the CRA project (in the lib both React and React DOM are listed as peer dependencies), but I always end with the same result. Is there something wrong with my config? This is the first time I use Rollup for creating a React component lib, so maybe there's something I missed
If the above info is insufficient, here's the GitHub repo
Thanks in advance
Edit: I just saw that I forgot to import React in my test project, however after doing so the results were the same, editing my original question just to fix that.
Update 1: I changed several configurations (changed deprecated rollup-plugins to their currently maintained versions, added globals to the output part of rollup.config.js, added namedExports to commonjs plugin configuration, added an external section specifying react, react-dom and prop-types), but now what I'm getting is a React is not defined error, updating the question with the new config

PrismJS babel config not working in React

I have babel-plugin-prismjs installed,
made .babelrc with this code:
{
"plugins": [
["prismjs", {
"languages": ["javascript", "css", "html"],
"plugins": ["line-numbers", "show-language", "copy-to-clipboard", "toolbar", "inline-color"],
"theme": "prism-tomorrow",
"css": true
}
]
]
}
and imported Prism to App.js.
This is the code in App.js:
import React, { useEffect } from "react";
import Prism from "prismjs";
const code = `
const foo = 'foo';
const bar = 'bar';
console.log(foo + bar);
`.trim()
function App() {
useEffect(() =>{
Prism.highlightAll();
}, [])
return (
<pre className="line-numbers">
<code className="language-js">
{code}
</code>
</pre>
);
}
export default App;
But the webpage is not highlighting the syntax. What am I missing here?
If you are using this in a react project built from create-react-app (CRA), your code will not work. Your .babelrc file is not being used. CRA does not expose babel configuration. Check this answer to understand more on this.
For your use-case the easiest way would be to go to prismjs page to download the CSS file. Select the right theme and click 'DOWNLOAD CSS' at the bottom of the page.
Import this CSS file in your App.js code.
import '../prism.css';
This will work for Javascript and some other built-in language packs. If proper markup for your language doesn't show up you will need to import those languages manually as well.
Note: This is just a work around to not ejecting your CRA project. The recommended way is still to use babel-plugin-prismjs if you can.

React Storybook SVG Failed to execute 'createElement' on 'Document'

I'm trying to add Storybook to an existing React app but getting errors with imported svg files. The svg is imported and used like:
import Border from './images/border.inline.svg'
...
<Border className="card__border" />
This works when the app is run and built, but I get an error in Storybook. How come?
Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/border.inline.258eb86a.svg') is not a valid name.
Error: Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/border.inline.258eb86a.svg') is not a valid name.
The default webpack.config.js has:
...
{
test: /\.inline.svg$/,
loader: 'svg-react-loader'
},
...
Also, the existing code uses webpack 3, and I'm using Storybook V4.
This is happening because Storybook's default webpack config has its own svg config:
{
test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/,
loader: 'file-loader',
query: { name: 'static/media/[name].[hash:8].[ext]' }
},
I'm pretty sure this is the cause, because you can see the path outlined in error message: query: { name: 'static/media/[name].[hash:8].[ext]' } -> static/media/border.inline.258eb86a.svg
The solution can be to find the existing loader & change / or add an exclude rule to it. Here's an example of a custom .storybook/webpack.config.js:
// storybook 4
module.exports = (_, _, config) => {
// storybook 5
module.exports = ({ config }) => {
const rules = config.module.rules;
// modify storybook's file-loader rule to avoid conflicts with your inline svg
const fileLoaderRule = rules.find(rule => rule.test.test('.svg'));
fileLoaderRule.exclude = /\.inline.svg$/;
rules.push({
test: /\.inline.svg$/,
...
}],
});
return config;
};
It appears that Storybook V6 they have changed the default Webpack config. I found that the above answers didn't work for me.
They no longer have an SVG rule, therefore testing for SVG will either error or return back undefined.
There is a oneOf rule on the module.rules which contains a loader without a test as the last rule:
{
loader: '/Users/alexwiley/Work/OneUp/resources/client/node_modules/react-scripts/node_modules/file-loader/dist/cjs.js',
exclude: [Array],
options: [Object]
}
This is the culprit, you need to make sure that the file load is excluding all inline SVG file otherwise it will error.
Add the following to your .storybook/main.js file:
webpackFinal: async(config, { configType }) => {
config.module.rules.forEach((rule) => {
if (rule.oneOf) {
// Iterate over the oneOf array and look for the file loader
rule.oneOf.forEach((oneOfRule) => {
if (oneOfRule.loader && oneOfRule.loader.test('file-loader')) {
// Exclude the inline SVGs from the file loader
oneOfRule.exclude.push(/\.inline\.svg$/);
}
});
// Push your SVG loader onto the end of the oneOf array
rule.oneOf.push({
test: /\.inline\.svg$/,
exclude: /node_modules/,
loader: 'svg-react-loader', // use whatever SVG loader you need
});
}
});
return config;
}
In Storybook 6, You have to import it like this:
import { ReactComponent as Border } from './images/border.inline.svg'
Try that if it also works for your version since this question is from a year ago.
This is another way that fixed the issue for me
import Border from './images/border.inline.svg'
And then in your code
<img src={Border} alt="Border" className="w-25"/>
I got it working with
...
module.exports = {
module: {
rules: [
{
test: /\.inline.svg$/,
loader: 'svg-react-loader'
}
]
}
}
For me it was happening as I was using wrong tag name:
import React from 'react';
import RMDBLogo from '../../images/react-movie-logo.svg';
import TMDBLogo from '../../images/tmdb_logo.svg';
import { Wrapper, Content,LogoImg,TMDBLogoImg } from './Header.styles';
const Header = () => (
<Wrapper>
<Content>
<LogoImg src={RMDBLogo} alt='RMDBLogo' />
<TMDBLogo src={TMDBLogo} alt='TMDBLogo'/>
</Content>
</Wrapper>
);
export default Header;
I had imported TMDBLogoImg component while I'm using TMDBLogo tag inside Content tag.

React loadable import connect component

I'm trying to make react-loadable work with ServerSide Rendering. I've got a quite big app using multiple HOCs which connect components, add routers etc.
Components seem to go okay, instead the smart components connected via react-redux or other HOCs.
react-loadable doesn't seem to so this as seperate modules and in the .json that is created undefined is thrown.
Example of dumb components loaded correctly:
"common/HeaderTitle": [
{
"id": 667,
"name": "./src/app/components/common/HeaderTitle.jsx",
"file": "0.650d44243e1a15fa7627.js"
},
{
"id": 667,
"name": "./src/app/components/common/HeaderTitle.jsx",
"file": "0.650d44243e1a15fa7627.js.map"
},
...
],
Example of smart components which don't load:
"undefined": [
{
"id": 626,
"name": "./src/app/components/modules/Home/index.jsx",
"file": "0.650d44243e1a15fa7627.js"
},
...
],
The html render of my server looks like the following. Everything in here is copied from the official docs:
app.use((req, res) => {
const modules = [];
const html = SSR ? renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={{}}>
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App />
</Loadable.Capture>
</StaticRouter>
</Provider>,
) : ' ';
console.log('modules', modules);
const bundles = getBundles(stats, modules);
console.log('bundles', bundles);
res.status(200).send(renderFullPage({ html, bundles }));
});
Which logs as expected:
modules [ './modules/Home' ]
bundles [ undefined ]
TypeError: Cannot read property 'file' of undefined
This is really driving me crazy. The package seems really good and I would like to use it. But I can't find anything about issues like this and there ain't an issue section on Github.
Open to any suggestion or help. Thanks in advance.
Just a wild guess, but I think it has something to do with the fact you're importing the index.js, and react-loadable doesn't pick this up in Loadable.Capture. Or the wepback plugin doesn't create the correct mapping in the react-loadable.json file.
A trick I used in a production app (complete with react-router, redux, react-loadable) is to use webpack's named chunks:
const AsyncComponent = Loadable({
loader: () => import(/* webpackChunkName: "myNamedChunk" */ './SomeComponent'),
modules: ['myNamedChunk']
});
This way you control the module name and explicitly tell react-loadable what to map.
I also wrote an article on how to achieve this in a create-react-app project: https://medium.com/bucharestjs/upgrading-a-create-react-app-project-to-a-ssr-code-splitting-setup-9da57df2040a

Resources