Adding/Overriding Webpack Loaders for a create-react-app? - reactjs

is there an option to add a loader for a certain file type to a boilerplate create-react-app application? Whatever I tried, I don't seem to be able to get it working without specifying the loader itself in the js file with:
!!import List from './data.txt'
I found the following link, but am hesitant to believe that the de-facto default way of creating a react app nowadays doesn't allow to override/add loaders (simply):
https://github.com/facebook/create-react-app/issues/2608
Thanks!

At my last project we used this:
npm i #craco/craco
https://www.npmjs.com/package/#craco/craco
It allows config changes without ejecting.

You can also do this:
in your react-app-env.d.ts add declare module "*.txt"
then in the code you can get the content in Promise:
import dataRaw from "./data.txt"
fetch(dataRaw)
.then(raw => raw.text())
.then(text => { <here you have it> }

Related

Create react app - how to copy pdf.worker.js file from pdfjs-dist/build to your project's output folder?

Since I can't use browser's pdf viewer in the network where the app is going to be used, I am testing a react-pdf package for loading PDF's with React.
I have made a component where I am sending a url of my PDF that I get from backend:
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
const PDFViewer = ({url}) => {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
function onLoadError(error) {
console.log(error);
}
function onSourceError(error) {
console.log(error);
}
return (
<div>
<Document
file={window.location.origin + url}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onLoadError}
onSourceError={onSourceError}
>
{[...Array(numPages).keys()].map((p) => (
<Page pageNumber={p + 1} />
))}
</Document>
</div>
);
};
export default PDFViewer;
But, on opening the PDFViewer I get an error
Error: Setting up fake worker failed: "Cannot read property 'WorkerMessageHandler' of undefined"
In documentation it says that you should set up service worker and that the recommended way is to do that with CDN:
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
But, I can't use CDN links for my project, and in the documentation it also says:
Create React App uses Webpack under the hood, but instructions for Webpack will not work. Standard instructions apply.
Standard (Browserify and others)
If you use Browserify or other bundling tools, you will have to make sure on your own that pdf.worker.js file from pdfjs-dist/build is copied to your project's output folder.
There are no instructions on how to do that with create-react-app. How can I set this up locally then?
Install pdfjs-dist
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
Reference: https://github.com/mozilla/pdf.js/issues/8305
found a more efficient way of including the worker
by including the library from the dependencies of react-pdf itself, this way you will never get a version mismatch like this The API version "2.3.45" does not match the Worker version "2.1.266"
if you install pdfjs-dist manually you will have to check react pdf dependency version on every build
import { Document, Page, pdfjs } from "react-pdf";
import pdfjsWorker from "react-pdf/node_modules/pdfjs-dist/build/pdf.worker.entry";
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
see similar error on pdfjs library : https://github.com/mozilla/pdf.js/issues/10997
hope it helps people
You can install worker loader module for webpack:
npm install worker-loader --save-dev
Then use it inline where you are going to work with a worker:
import SomeWorker from 'worker-loader?inline=true!../workers/some.worker'
const someWorker: Worker = new SomeWorker()
someWorker.postMessage(...)
I haven't tried this solution with react-pdf, but it might help.
You may need to add types for TypeScript if you are using it:
declare module 'worker-loader*' {
class SomeWorker extends Worker {
constructor()
}
export default SomeWorker
}
Just to add that in some .d.ts file in your project.
Install pdfjs-dist then use the webpack module:
import { pdfjs } from 'react-pdf'
import worker from 'pdfjs-dist/webpack'
pdfjs.GlobalWorkerOptions.workerSrc = worker
If your build process uses cli commands, (i.e. AWS buildspec), you can use this:
mkdir -p build && cp ./node_modules/pdfjs-dist/build/pdf.worker.js build
If you are in a corporate codebase environment and have little to no experience configuring WebPack, I wanted to share a little more info if (like me) you struggled with this for quite a long time.
My environment has several complicated WebPack config files (base, production, and development), and the resolution ended up being pretty simple but it escaped me for quite a while because I was unfamiliar with the complicated build process.
1) The Implementation
Quite simple, just as the docs recommend (I went with the minified file). Our React environment required me to use React-PDF#4.2.0, but there aren't any differences here.
import {Document, Page, pdfjs} from 'react-pdf'
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.min.js'
Note: a previous solution recommended grabbing the source from the react-pdf node_modules folder, however, my codebase is setup to install dependencies separately somehow because when I npm install react-pdf, pdfjs-dist is also installed separately. Regardless, this method did not work for my codebase (importing the worker as a variable) due to the way the project is built. The import command acted like it couldn't find the proper named export inside a node_modules folder. It was top-level or nothing.
2) WebPack Config
Since I do not know WebPack at all, but found pretty easily that what I needed to do was take advantage of CopyWebpackPlugin, I searched through those existing dev and prod webpack config files, and found existing copy commands for JQuery and polyfill and added a new plugin to that array:
new CopyWebpackPlugin({from: 'node_modules/pdfjs-dist/build/pdf.worker.min.js})
I had to do this in multiples places in both config files as this large project has several entry point server files for the different services of the website.
3) Inserting Script Tag to HTML Head
This was the crucial part I was missing. There was a "ComponentFactory" file whose job it was to insert chunks of html in the <head> and tail of the html file. I wasn't used to something like this on small projects. So there, I simply copied what was already done for the jquery and polyfill, which included a string literal of the location of the assets folder the webpack was building out to. In my case, that was something like "assets/v1/". So the tag looked like this:
<script src=`${STATIC_ASSETS_URL}/pdf.worker.min.js` defer></script>
It works perfectly, however I am still getting the "Setting Up a Fake Worker" but immediately after that, it loaded it successfully in console and checking the dev tools, it was using the proper file. It's probably just a timing thing of the src set not running high enough in the code, but it was not effecting the outcome, so I let it go.
(Sidebar, if you also get the "TT unknown function" (paraphrasing) error, that can be ignored. It's just a font issue with whatever PDF you are loading and is just a warning, not an error.)
I was facing this issue once I had to use "react-pdf" from within a package.
It was solved by importing the worker conditionally into the code:
Conditional import:
export const getWorker = () => {
try {
return require('react-pdf/node_modules/pdfjs-dist/legacy/build/pdf.worker.entry.js')
} catch () {
return require('pdfjs-dist/legacy/build/pdf.worker.entry.js')
}
}
usage:
import { Document, Page, pdfjs } from 'react-pdf/dist/umd/entry.webpack'
pdfjs.GlobalWorkerOptions.workerSrc = getWorker()

How to import a file into a react app that uses create react app as raw text?

Goals
I want to display some code for reference from the project itself.
I want the display of the code to be updated with the implementation.
I don't want to eject from create-react-app
This react project, created with create-react-app and typescript, is going to be used to display some custom components for re-use in other projects. My goal is to have the component be used right next to the code that is using it.
How can I load the file if I don't have access to the webpack config, and I can't use fs.readFile?
I managed to get this working after a bit of looking around. There are two major pieces that had to be in place to make it work.
Use the appropriate loader
In this case I wanted to use the raw-loader, so I installed it as a dev dependency. yarn add -D raw-loader.
In order to actually import the file I needed to then override the webpack configuration like this:
// eslint-disable-next-line import/no-webpack-loader-syntax
import toolbarItems from '!!raw-loader!../ToolbarItems';
This loads the entire file into the variable toolbarItems. By using the !! before the loader I prevent any other webpack loaders from processing it in this specific case. This might work on its own in a plain javascript project, but in typescript...
You must provide a module to typescript
I was running into the typescript error:
Failed to compile.
/Users/cory/Code/something/custom-theme/src/pages/NavigationDemo.tsx
TypeScript error in /Users/cory/Code/something/custom-theme/src/pages/NavigationDemo.tsx(9,26):
Cannot find module '!!raw-loader!../ToolbarItems'. TS2307
7 |
8 | // eslint-disable-next-line import/no-webpack-loader-syntax
> 9 | import toolbarItems from '!!raw-loader!../ToolbarItems';
| ^
10 |
11 | const useStyles = makeStyles({
12 | root: {
Simply declaring a module for the loader in a file called ToolbarItems.ts.d.ts solved the issue for me:
declare module '!!raw-loader!*' {
const content: string;
export default content;
}
source
Since you use create-react-app for your project, the best solution at the moment would be a babel plugin called Raw.Macro.
This plugin allows you to access content of your files without a need to create-react-app eject. Provides really elegant solution without any boilerplate code and declaring "d.ts" files as in previous answer.
Note: There is a small drawback that you have to re-start your webpack dev server when the file changes, because the content of a file is being embedded during the build process.
import raw from 'raw.macro';
function foo(){
const jsonContent = raw('../utils/stops.json');
console.log(jsonContent);
}

import/export according to environment variable

For a clientSide app I would like to select a specific import according to the environment variables that were setup in the package.json.
eg:`
if (process.env.IS_DEV)
import { store } from '../../../index.js
else
import { store } from './index.js';
`
Is there anyway to do this.
I currently receive the error -
Parsing error: 'import' and 'export' may only appear at the top level
As the error says, import can be only at top level.
If you are using some bundler such as webpack or parcel, you can use a require instead.
Pay attention: that both of the implementations will be inside the bundle, and only one of them will be executed.
You can use web pack dynamic import to enable this if you are using web pack 1
$ npm install babel-plugin-dynamic-import-webpack --save-dev
then in .babelrc
{
"plugins": ["dynamic-import-webpack"]
}
https://github.com/airbnb/babel-plugin-dynamic-import-webpack
in the newer versions of web pack, you can do this without Babel
https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import
the other solution which mentioned before is using require
but i think here you can do it in a different way to avoid having this files in the bundled result you can add build script to run before building the dist which replace this import inside the file completely before bundle it to frontend app
Maybe is better to check process.env.IS_DEV inside the store file and export different values based on the current env

Parcel-Bundler how to add SVG like how react does it

I am using parcel with typescript and out the box everything just works,
Now when I try to include svg it didn't work,
This is because I needed to change in typescript
declare module "*.svg"
This allows me to now compile my typescript.
Now the other thing that doesn't work is now I want to import my SVG component
and just use it like create-react-app 2
Import Icon from "./icon.svg"
function DisplayIcon(){
return <Icon/>
}
So this looked straightforward
https://www.npmjs.com/package/#svgr/parcel-plugin-svgr
yarn install and I get Can't resolve
<Typescript file>:<Line>:<Column>: Cannot resolve dependency './icon.svg' at '<Icon Location>'
So I think maybe it's the plugin and I try to use
https://www.npmjs.com/package/parcel-plugin-inlinesvg
I get the same issue.
What is it that I am missing?
I see some people have .babelrc files but the plugins should just work out the box atleast that is what the documentation says.
Using plugins in Parcel could not be any simpler. All you need to do
is install and save them in your package.json. Plugins should be named
with the prefix parcel-plugin- or #your-scope/parcel-plugin-, e.g.
parcel-plugin-foo or #your-scope/parcel-plugin-foo. Any dependencies
listed in package.json with these prefixes will automatically be
loaded during initialization.
It seems like the plugins boot, but nothing works.
Without the plugins I get the file in my public path with a string to it in my JavaScript runtime.
What am I missing and what am I doing wrong?

create-react-app markdown-loader for webpack

I've cloned the create-react-app and I would like to use the webpack plugin markdown-loader. Can someone please advise me how I would modify the webpack.config.dev.js to do so.
Thanks
If you don't want to eject out of create-react-app, it's actually fairly simple to do with loader directives.
Install markdown-loader to turn the markdown into HTML
Install html-loader to be able to load HTML into JS
Then:
import YourMarkdown from '!html-loader!markdown-loader!./YOURFILE.md'
export default function MarkdownComponent() {
return <div dangerouslySetInnerHTML={{ __html: YourMarkdown }} />
}
Taken from Dan Abramovs' post here: https://facebook.github.io/react/blog/2016/07/22/create-apps-with-no-configuration.html
All the build settings are preconfigured and can’t be changed.
If you wish to modify any of the settings you can Eject from the app.
“Ejecting” lets you leave the comfort of Create React App setup at any time. You run a single command, and all the build dependencies, configs, and scripts are moved right into your project. At this point you can customize everything you want, but effectively you are forking our configuration and going your own way.
npm run eject will cause all the config options to be moved over to your application giving you full control over the config. - This is a one way process.

Resources