How to use React components on WebWorker - reactjs

I am creating a PDF generator using the react-pdf package at https://react-pdf.org/. The process of converting the react-pdf components into a pdf blocks the main thread, so I would like to convert them on a separate Worker thread.
I am using create-react-app with worker-loader to recognize WebWorker files. I am having a hard time coming up with a way to import the react components into the Webworker and convert them using the pdf(Component).toBlob() method provided by react-pdf. The pdf(Component).toBlob() takes a react component as input and outputs the blob files, this means that the worker has to somehow be able to load the React component. The WebWorker works as expected when no React components are included.
I naively tried importing the React components into the WebWorker and tried running the conversion method:
pdf(<Component />).toBlob().then(blob=>{
...
})
which causes a module parse error:
Uncaught Error: Module parse failed: Unexpected token (14:8)
File was processed with these loaders:
* ./node_modules/eslint-loader/index.js
You may need an additional loader to handle the result of these loaders.
Any help would be extremely appreciated.
Edit:
I am using the create-react-app default webpack configuration with the addition of worker-loader which detects .worker.js files and registers them as Webworker. I added the work-loader using the react-app-rewired package:
module.exports = function override(config, env) {
config.module.rules.push({
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
})
config.output['globalObject'] = 'this';
return config;
}

This answer is an extension of the discussion we had in the chat.
The issue at hand here is the .push() from the override function. As stated in the MSDN documentation:
The push() method adds one or more elements to the end of an array and returns the new length of the array.
Effectively the config.module.rules.push() is adding the loader at the end of the rules array, thus processing first the React code and then the worker.js.
As Ali Zeaiter found out the solution was to use .unshift() (cf. doc) instead of .push() in order to prepend the loader to the rules array and thus processing first the worker.js file.

Related

Import a file as a string (or source asset) in Gatsby / React

I want to import .ts, .tsx, .js, and .jsx files into a react component and render them within a PrismJS highlighting block. For example, let's say I have a TypeScript file with functionA in it that I want to highlight in my actual website:
functionA.ts:
export function functionA() {
console.log("I am function A!");
}
I want to include this in a different component. The problem is, when I import it, I am obviously importing the webpack module version of it. My weak attempt at trying to get my function render in a react component looks like this:
MyComponent.tsx:
import * as React from "react"
import { functionA } from "./functionA"
export function MyComponent() {
return (
<>
<h1>Here is your code block:</h1>
<pre>
<code>
{functionA.toString()}
</code>
</pre>
</>
)
}
and what will actually render on the page where the code block is will look something like this:
Here is your code block:
WEBPACK__IMPORT.functionA() {
console.log("I am function A!")
}
I can't exactly remember what the .toString() function output looked like, but the point is it is NOT just the contents of the file how it appears in a code edit for example - it has been modulized by WebPack.
So, in a Gatsby project, how can i get these various code snippets to be imported directly as a string, purely as they are written, without WebPack enacting its import stuff on it? Is there a plugin or some way to tell Webpack to use the imported file as its asset/source module type? I know for MD or MDX files there is the gatsby-remark-embed-snippet, but I am building a component based HTML page and can't use MD or MDX files!
It's very late, and perhaps I just can't see the forest from the trees, I know there must be a way to do this...
You need to require the file using webpack's raw-loader, i.e:
const functionA = require("!!raw-loader!./functionA");
This works for create-react-app, as in the solution discussed here, and this works for Gatsby as well!
After using require on such a file, the file contents can be rendered in the component as:
<pre>{functionA.default.toString()}</pre>
It's then up to you to add syntax highlighting using a tool like prism or similar.
Note this solution will only work as long as Gatsby V3 continues to use WebPack v4, as raw-loader is deprecated in WebPack v5 and will be phased out for asset/source type modules.

Why do I have to use "require" instead of "import from" for an image in React?

I see that this answer suggests the syntax for importing images as shown below (commented out). In my case, it didn't work out (complaining there's no modules to find in that file) and I had to switch to the syntax that's currently active.
// import Author from "../assets/author.png";
var Author = require("../assets/author.png");
The difference I can imagine is that I'm using TypeScript (transpiling my TSX by awesome-typescript-loader and loading my PNG file-loader) and they seem to use JSX. But as far my understanding goes, it all transpiles to plain JS and require in the end.
Being a noob on React, I'm not sure what the reason of this discrepancy is but also I'm not sure what to google for to investigate myself.
This is more of a problem with typescript than webpack itself, you might need to declare modules on a declaration file.
Create a declarations.d.ts
Update your tsconfig.json
"include": [
"./declarations.d.ts",
],
Put this on that file:
declare module '*.png';
Error might be gone.
You can declare a module for your images like this:
declare module "*.png" {
const value: any;
export default value;
}
Then, you will be able to import your image like this:
import AuthorSrc from "../assets/author.png";
This is happening because webpack doesn't support image import out of the box. So you need to add a rule for that in the webpack config file. When you add a new rule, TypeScript doesn't automatically know that, so you need to declare a new module to resolve this. Without the module, you will be able to import images, but TypeScript will throw an error because you didn't tell to it is possible.
This issue has nothing to do with webpack or any bundler and is not quite a problem with typescript.
Typescript has stated that `require("path") is a way to include modules to the scope of your current module, whilst it can be also used to read some random files (such as json files, for example).
As Vincent and Playma256 specified, you can declare a module wildcard to match certain file types, so you can import it as an import statement. But you don't really need to do this. Typescript won't give you an error if you are trying to import a png or a json file (tslint might, but that depends on your configuration).
By the way, if your declaration is within the source folder of your project as defined in tsconfig.json, you don't need to include it as specified by Playma256.
I've created a sample project in node for you to test:
https://github.com/rodrigoelp/typescript-declare-files
I think you can solve this problem with Webpack&&typescript.The official webpage of webpack has introduced something about this in
https://webpack.js.org/guides/typescript/
And I have try this myself in
https://github.com/reactpersopnal/webpack-root/tree/feature/typescript
The reason is that you would like to use non-code assets with TypeScript, so we need to defer the type for these imports for webpack.
Your could simply add custom.d.ts.
declare module "*.jpg" {
const content: any;
export default content;
}

Including an external js script in a React Component

I have a react application, managed using react-scripts.
In my app i am using an external js script. The external script doesn't do any module exports.
The structure of the external script is as below (too big to include it in full).
var TradingView = {.... various functions }
At the end of the file:
if (window.TradingView && jQuery) {
jQuery.extend(window.TradingView, TradingView)
} else {
window.TradingView = TradingView
}
My goal is to create a simple react component using the external script, and call the function: TradingView.widget({...});
I have been searching online for ways to include an external script in a react component/ES6 style, and have tried various options: react-async-script-loader, and various webpack plugins: script-loader, imports-loader, ProvidePlugin etc. But i haven't been able to make it work.
The error i am getting after using the imports-loader or ProvidePlugin is:
1189:31 error 'jQuery' is not defined no-undef
1190:9 error 'jQuery' is not defined no-undef
In my webpack config, i have:
In the loaders section:
{
test: /tv\.exec\.js/,
loader: 'imports?jQuery=jquery,$=jquery,this=>window'
}
In the plugins section:
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
When i create a simple webpage (no react), and include the script, and call TradingView.widget(), the page just works fine.
The closest i could get help was to look at:
Managing jQuery plugin dependency in webpack
But it didn't work for me. I am quite new to the react, webpack ecosystem, so I am not sure what i am missing here.
Please let me know if you need any additional info.
Update: I tested the above, including the script in a simple react component, but not using the react-scripts this time, and directly using webpack configurations. I was simply able to import the external js in my component directly, and it worked. I was also able to use imports-loader plugin in webpack to expose jQuery, which also worked. So its possible that using react-scripts needs something else to make it work.
It looks like your external script is handling its "exports" by adding them as members of window. You can use the import keyword on a file that doesn't define exports like so:
import "modulename";
There's nothing special about that syntax except that it doesn't imply that any functions or variables will be made available via the import facility. The code in "modulename" that assigns members to .window will execute, which is the important thing.
For compiler complaints about accessing window.* globals, try assigning the variable you want to access to a local variable:
const jquery = window.jquery;
...or maybe...
const TradingView = window.TradingView;
Then you'll have the variable in scope, and it should be usable.

Initial static React HTML rendering with Webpack

Is there a way to pre-generate the HTML structure of a (single route) React application directly in the HTML entry point?
Then the page will be able to display HTML (based on React initial state) before any JS is loaded.
I'm actually using webpack-html-loader but any other loader or plugin is welcome ;)
PS: May static-site-generator-webpack-plugin be of any help?
PS: I'm not using React Router
If you want to use static-site-generator-webpack-plugin you first need to build a bundle with webpack bundle.js that exports a render function that takes following arguments.
locals an object with various page metadata e.g. title that go into component parameters (traditionally thought of as template variables).
callback a nodejs style (err, result) callback that you will call with your rendered html as the value for result
e.g.
// entry.js, compiled to bundle.js by webpack
module.exports = function render(locals, callback) {
callback(null,
'<html>' + locals.greet + ' from ' + locals.path + '</html>');
};
It is in this function that you will instantiate your components (possibly via React Router if you want) and render them with ReactDOMServer.renderToString().
You will then specify the compiled bundle.js as bundle in your instantiation of StaticSiteGeneratorPlugin as well as your concrete routes in paths and in locals an object containing the above mentioned metadata values.
var paths, locals; // compute paths from metadata files or frontmatter
module.exports = {
entry: {
bundle: './entry.js' // build bundle.js from entry.js source
},
...,
plugins: [
new StaticSiteGeneratorPlugin('bundle', paths, locals)
]
}
The keys you specify for locals in webpack.config.js in will be present in the locals parameter of every call to render(locals, callback). They will be merged with path, assets and webpackStats keys provided by the plugin.
If you want to load javascript code into your pages after rendering you could compile an additional page.js entry to your webpack config that calls ReactDOM.render() in the typical manner and then load that bundle in a script tag emitted by in your render(locals, callback) function in your bundle.js (above). Ensure that page.js mounts components to the same location in the DOM as they are when rendered by entry.js (you will probably set an id attribute on the parent element). You will also need to ensure that any location (i.e. route path) dependent variables align in both environments.
Check out the source code of Gatsby which also uses this plugin. You can also have a look at the source code for Phenomic for an alternative approach.
You should try server side rendering, it will let react render the first view of your app in a backend and deliver a static HTML. This boilerplate already comes with server rendering set up and you can learn more about it here
Jekyll is great static site generator which can be extended with custom ruby plugins. You need to enable WebPack to make Jekyll calls. See Plugging Webpack to Jekyll Powered Pages
Here you have a working example https://github.com/aganglada/preact-minimal/blob/master/config/webpack.config.js, if you like you can fork it and take a look at how this work all together.
Hope it helps :)

Webpack configuration with react class containing server code

I have an isomorphic react/flux (alt) implementation.
I'm trying to transform the project from browserify to webpack.
Considering my React class could look like this:
var dataAccess = require('../server/data-access');
var MyReactClass = React.createClass({
statics: {
/**
* This gets called by the containing component, so that each class
* handles its own data fetching.
* This part is obviously irrelevant in the client since the
* fetching is only done server-side and the result is used
* to populate the Alt.js store
*/
getData: function () {
return dataAccess.fetchData(
);
}
}...
I'd like to avoid bundling the entire "server" folder when running webpack, but when I do (via exclude in the loader config), things break in a strange way:
ERROR in ./react/react-routes.js
Module parse failed: C:\project\react\react-routes.js Line 19: Unexpected token <
You may need an appropriate loader to handle this file type.
| var routes = (
|
| <Route name="App" path="/" handler={App}>
|
| <DefaultRoute name="Default Route" handler={Main}/>
# ./client/index.js 5:18-50
If I return "server" back I'm getting errors on transforming mongojs's dependencies ("net" and such), which I don't believe is the correct direction.
If I remove all requires to my react classes in react-routes.js webpack is able to complete successfully. From this I understand that the problem indeed is in the fact that my classes have server code in them.
In browserify I was able to overcome this by using the require for the "data-access" and other server files inside the function getData(), but with webpack I didn't have luck so far.
What should be my ignore/exclude rules and how should I implement them?
Thanks.
What if you solved it outside of Webpack? You could do npm i babel --save-dev and then trigger require('babel/register') before importing that dataAccess bit. It will be able to deal with the JSX then. That would feel like the simplest solution to me.
React with Flux here is my github repository hope it will helps to understand the basic of react with flux https://github.com/TameshwarNirmalkar/ES6Babel it has:
Webpack
ES6
Babel
Eslint
React
Flux
Json-server for rest api
Complete CRUD operation
Hope it would be helpful.

Resources