How to use React.lazy in Gatsby - node.js

As using React.lazy in Gatsby you'll get error when making production( gatsby build), what is the best way to use React.lazy and suspense in Gatsby Project

React.lazy and Suspense are still not ready for server-side rendering, but they can still be used by checking that the code is executed only on the client. While this solution is inferior to loadable-components, that works both on server side and client, it still provides an alternative for dealing with client-side only packages, without an added dependency. Remember that the following code could break if executed without the isSSR guard.
import React from "react"
const ClientSideOnlyLazy = React.lazy(() =>
import("../components/ClientSideOnly")
)
const MyPage = () => {
const isSSR = typeof window === "undefined"
return (
<>
{!isSSR && (
<React.Suspense fallback={<div />}>
<ClientSideOnlyLazy />
</React.Suspense>
)}
</>
)
}
Try following this guide on gatsbyjs.com, if the above doesn't work for you

Related

How to write an npm package that uses crypto in browser but falls back to node:crypto on server

I maintain a package called react-querybuilder that generates unique identifiers using crypto.getRandomValues().
When used in the browser, this is not a problem as crypto is available pretty much everywhere now. But react-querybuilder can be used on the server for query processing (including generating IDs) as well as server-side rendering, which means the crypto package must either be polyfilled with crypto-browserify or loaded manually (something like globalThis.crypto = require('node:crypto')).
Some build setups require the polyfill even if they don't do SSR, e.g. Create React App v5 since it uses Webpack v5 (which doesn't automatically include polyfills like v4 did). CRA v4 doesn't have the issue.
How can I write the package to use window.crypto when running in the browser, but load node:crypto and use that instead when running on the server? Ideally the setup would avoid requiring anything extra of the end user (polyfills, config, etc.).
This is the current implementation at time of writing (comments removed):
let cryptoModule = globalThis.crypto;
if (!cryptoModule && typeof require === "function") {
cryptoModule = require("crypto").webcrypto;
}
const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
const position19vals = ["8", "9", "a", "b"];
const re = /[xy]/g;
const container = new Uint32Array(32);
export const generateID = () => {
cryptoModule.getRandomValues(container);
let i = -1;
return template.replaceAll(re, (char) => {
i++;
return char === "y"
? position19vals[container[i] % 4]
: (container[i] % 16).toString(16);
});
};
FWIW, I'm not opposed to having separate builds for client and server if that makes it easier to implement (for me and the end user, but mostly the end user).

How to include dom-manipulating scripts into SSR Next.js App

I am experiencing following error:
Warning: Text content did not match. Server: "But I want to be altered by the client" Client: "Test"
in div (at pages/index.tsx:17)
in div (at pages/index.tsx:6)
in HomePage (at _app.tsx:5)
in MyApp
in Container (created by AppContainer)
in AppContainer
... with the following setup:
A NextJS App Component:
function HomePage() {
return (
<>
<div id="test-div">I am rendered on the server.</div>
<script src="http://localhost:8080/bundle.js"></script>
</>
);
}
export default HomePage;
(Note: The URL http://localhost:8080/bundle.js assumes webpack-dev-server is running and serving that resource)
The included "example" script:
const element = document.getElementById('test-div') as any;
element.innerHTML = 'But I want to be altered by the client';
In a simple setup I would just have a static html file, declaring a div element and including the "example" script.
But I would like to use NextJS, because I want to render dynamic (SSR) content into the page (eg. Text contents from a cms).
I noticed, that sometimes (if script execution takes some more ms of time), there is no error. Just do something time consuming in the example script.
Another hacky approach is to use setTimeout in the example script.
I don't want to do that until I know why this is happening:
setTimeout(function() {
const element = document.getElementById('test-div') as any;
element.innerHTML = 'But I want to be altered by the client';
}, 20);
Next.js 11.0.0 and above
You can use Next.js Script component to load third-party scripts.
// pages/index.js
import Script from 'next/script'
function Home() {
return (
<>
<Script src="https://www.google-analytics.com/analytics.js" />
</>
)
}
With next/script, you can define the strategy property and Next.js will optimize loading for the script.
Before Next.js 11.0.0
Browser and document, window objects are not available during server-side rendering.
You can initialize scripts that manipulate DOM after React component did mount.
useEffect(() => init(), [])
To add an external script you can do the following:
useEffect(() => require('../babylon.js'), [])
To include a script from another server you can add a script tag:
useEffect(() => {
const script = document.createElement("script");
script.src = "http://localhost:8080/bundle.js";
script.async = true;
document.body.appendChild(script);
},[])
If you're adding DOM listeners you would also need to do cleanup.
Using the Effect Hook
Effects with Cleanup

Why is `window` undefined when running my Webpack plug-in after upgrading to Webpack 4?

I've written a plug-in for Webpack that takes my generated React component, renders it in Node, and then inserts it into the generated HTML document.
This used to work fine in Webpack 3. To upgrade it to Webpack 4, I replaced
compiler.plugin("after-emit", compilation => {
with
compiler.hooks.afterEmit.tap('prerender-plugin', (compilation) => {
which, as far as I can see, should be sufficient.
In the plug-in, I ask Webpack for the path to my generated bundle and then require that, roughly as follows:
const assetHash = Object.keys(compilation.assets).filter(asset => /app(.*).js/.test(asset))[0];
const appFilePath = compilation.assets[assetHash].existsAt;
// The `.default` is needed because it's an ES2015 module
const App = require(appFilePath).default;
However, I then all of a sudden hit errors like the following as a result of the require:
ReferenceError: window is not defined
at Object.<anonymous> (/home/vincent/Workspace/Flockademic/stacks/frontend/dist/app.da5512cf55a3c4446086.js:1:286)
...
The offending code seems to be some standard Webpack top matter:
!function(e,i){if("object"==typeof exports&&"object"==typeof module)module.exports=i();else if("function"==typeof define&&define.amd)define([],i);else{var a=i();for(var o in a)("object"==typeof exports?exports:e)[o]=a[o]}}(window,function(){return function(e){var i={};function a(o){if
(Note that this sample is up to the supposedly-offending column. As you can see, window is not actually referred to on column 286.)
I'm not that familiar with Webpack, and documentation on plug-ins relatively scarce, so any pointers on how to get closer to solving this are very much welcome/
Add this at your root .js file will probably work.
if (typeof window === 'undefined') {
global.window = {};
}

Redux-Observable breaks on server-side rendering

I have a test repo with redux-observable
It works with webpack-dev-server but breaks with server-side-rendering giving:
TypeError: action$.ofType(...).delay is not a function
How to reproduce:
yarn dev works okay (webpack-dev-server).
yarn build && yarn start - runs node server-side-rendering which is breaking when creating store with redux createStore method.
It recognizes imported operators from rxjs within a browser (webpack-dev-server). My guess it might be a problem with webpack serverConfig, more specifically with:
externals: fs.readdirSync('./node_modules').concat([
'react-dom/server',
]).reduce((ext, mod) => {
ext[mod] = `commonjs ${mod}`;
return ext;
}, {}),
importing the whole rxjs library will jeopardise your tree shaking.
use pipe instead.
import { delay } from 'rxjs/operators';
const epic = action$ => action$
.ofType('baz')
.pipe(delay(5000))
.mapTo({ type: 'bar' });
;
It turned out I had to include rxjs in server.js where express is like:
import `rxjs`;
But I would swear I tried that solution before I posting a question.

Detect if called through require or directly by command line

How can I detect whether my Node.JS file was called using SH:node path-to-file or JS:require('path-to-file')?
This is the Node.JS equivalent to my previous question in Perl: How can I run my Perl script only if it wasn't loaded with require?
if (require.main === module) {
console.log('called directly');
} else {
console.log('required as a module');
}
See documentation for this here: https://nodejs.org/docs/latest/api/modules.html#modules_accessing_the_main_module
There is another, slightly shorter way (not outlined in the mentioned docs).
var runningAsScript = !module.parent;
I outlined more details about how this all works under the hood in this blog post.
For those using ES Modules (and Node 10.12+), you can use import.meta.url:
import path from 'path';
import { fileURLToPath } from 'url'
const nodePath = path.resolve(process.argv[1]);
const modulePath = path.resolve(fileURLToPath(import.meta.url))
const isRunningDirectlyViaCLI = nodePath === modulePath
Things like require.main, module.parent and __dirname/__filename aren’t available in ESM.
Note: If using ESLint it may choke on this syntax, in which case you’ll need to update to ESLint ^7.2.0 and turn your ecmaVersion up to 11 (2020).
More info: process.argv, import.meta.url
I was a little confused by the terminology used in the explanation(s). So I had to do a couple quick tests.
I found that these produce the same results:
var isCLI = !module.parent;
var isCLI = require.main === module;
And for the other confused people (and to answer the question directly):
var isCLI = require.main === module;
var wasRequired = !isCLI;
Try this if you are using ES6 modules:
if (process.mainModule.filename === __filename) {
console.log('running as main module')
}
I always find myself trying to recall how to write this goddamn code snippet, so I decided to create a simple module for it. It took me a bit to make it work since accessing caller's module info is not straightforward, but it was fun to see how it could be done.
So the idea is to call a module and ask it if the caller module is the main one. We have to figure out the module of the caller function. My first approach was a variation of the accepted answer:
module.exports = function () {
return require.main === module.parent;
};
But that is not guaranteed to work. module.parent points to the module which loaded us into memory, not the one calling us. If it is the caller module that loaded this helper module into memory, we're good. But if it isn't, it won't work. So we need to try something else. My solution was to generate a stack trace and get the caller's module name from there:
module.exports = function () {
// generate a stack trace
const stack = (new Error()).stack;
// the third line refers to our caller
const stackLine = stack.split("\n")[2];
// extract the module name from that line
const callerModuleName = /\((.*):\d+:\d+\)$/.exec(stackLine)[1];
return require.main.filename === callerModuleName;
};
Save this as is-main-module.js and now you can do:
const isMainModule = require("./is-main-module");
if (isMainModule()) {
console.info("called directly");
} else {
console.info("required as a module");
}
Which is easier to remember.
First, let's define the problem better. My assumption is that what you are really looking for is whether your script owns process.argv (i.e. whether your script is responsible for processing process.argv). With this assumption in mind, the code and tests below are accurate.
module.parent works excellently, but it is deprecated for good reasons (a module might have multiple parents, in which case module.parent only represents the first parent), so use the following future-proof condition to cover all cases:
if (
typeof process === 'object' && process && process.argv
&& (
(
typeof module === 'object' && module
&& (
!module.parent
|| require.main === module
|| (process.mainModule && process.mainModule.filename === __filename)
|| (__filename === "[stdin]" && __dirname === ".")
)
)
|| (
typeof document === "object"
&& (function() {
var scripts = document.getElementsByTagName("script");
try { // in case we are in a special environment without path
var normalize = require("path").normalize;
for (var i=0,len=scripts.length|0; i < len; i=i+1|0)
if (normalize(scripts[i].src.replace(/^file:/i,"")) === __filename)
return true;
} catch(e) {}
})()
)
)
) {
// this module is top-level and invoked directly by the CLI
console.log("Invoked from CLI");
} else {
console.log("Not invoked from CLI");
}
It works correctly in all of the scripts in all of the following cases and never throws any errors†:
Requiring the script (e.x. require('./main.js'))
Directly invoking the script (e.x. nodejs cli.js)
Preloading another script (e.x. nodejs -r main.js cli.js)
Piping into node CLI (e.x. cat cli.js | nodejs)
Piping with preloading (e.x. cat cli.js | nodejs -r main.js)
In workers (e.x. new Worker('./worker.js'))
In evaled workers (e.x. new Worker('if (<test for CLI>) ...', {eval: true}))
Inside ES6 modules (e.x. nodejs --experimental-modules cli-es6.js)
Modules with preload (e.x. nodejs --experimental-modules -r main-es6.js cli-es6.js)
Piped ES6 modules (e.x. cat cli-es6.js | nodejs --experimental-modules)
Pipe+preload module (e.x. cat cli-es6.js | nodejs --experimental-modules -r main-es6.js)
In the browser (in which case, CLI is false because there is no process.argv)
In mixed browser+server environments (e.x. ElectronJS, in which case both inline scripts and all modules loaded via <script> tags are considered CLI)
The only case where is does not work is when you preload the top-level script (e.x. nodejs -r cli.js cli.js). This problem cannot be solved by piping (e.x. cat cli.js | nodejs -r cli.js) because that executes the script twice (once as a required module and once as top-level). I do not believe there is any possible fix for this because there is no way to know what the main script will be from inside a preloaded script.
† Theoretically, errors might be thrown from inside of a getter for an object (e.x. if someone were crazy enough to do Object.defineProperty(globalThis, "process", { get(){throw 0} });), however this will never happen under default circumstances for the properties used in the code snippet in any environment.
How can I detect whether my node.js file was called directly from console (windows and unix systems) or loaded using the ESM module import ( import {foo} from 'bar.js')
Such functionality is not exposed. For the moment you should separate your cli and library logic into separate files.
Answer from node.js core contributor devsnek replying to nodejs/help/issues/2420
It's the right answer in my point of view

Resources