cannot use import statement outside a module with Next.js - reactjs

I need to import a npm package but failed when I use "import" statement
like this
import { cuteLuna } from 'lunacomponent';
and I got an error : cannot use import statement outside a module
after I change it to dynamic import, it works.
const cuteLuna = dynamic(() => import('lunacomponent').then((a) => a.cuteLuna), {ssr: false});
My question is, why should I use dynamic import instead of usual import?
thanks!!

Since Next.js is a framework that runs on server & client side it needs to consume the proper module styles for each.
Server side runs on Node, therefore your lib must expose a commonjs.
From your error I can guess that your lunacomponent lib is not exporting cjs files, therefore it fails on the server, when you use dynamic with ssr:false you tell Next.js to skip server-side run, therefore you don't have the same error.
I wasn't able to find this lunacomponent lib on the public npm registry, therefore I can't check my assumption.

Related

Tree shaking from remote import

This question might seem unusual and a little anti-pattern but at this stage I am just trying to figure out what is possible.
The situation is that I have a few components which are available from a remote import (via Webpack 5's Module Federation). The caveat is that I don't want to lazily load them.
Once imported, the components are passed into a HOC to enrich some functionality and then exported:
import ComponentFooRemote from 'testRemote/Foo'
import ComponentBarRemote from 'testRemote/Bar'
const ComponentFoo = withEnrichedFunctionality(ComponentFooRemote)
const ComponentBar = withEnrichedFunctionality(ComponentBarRemote)
export {
ComponentFoo,
ComponentBar,
}
Functionally this works as expected. The components can be imported, rendered, and no components are loaded twice.
The issue is that the code imported from the remotes are always loaded. If I don't use ComponentBar - or even if I delete ComponentBar - the code from the remote will be downloaded as long as the original import is present. This is happening when using both development and production mode in the Webpack config.
Does anyone know if I can tree-shake these imports or restructure the code to better optimise the performance? Ideally I would like to import the components from the same path.

web3.js to use with web development

I want to use web3.js together with my web page but the require function is not working for me. I have tried using browserify , importing instead of declaring as const but none worked, one problem solution lead to another problem. I tried to bundle these but web3 module has also some js inside which uses import statement so I am getting error to bundle them as well.
issue while using web3js with require
simple js code
issue while using web3js with import
simple js code
The import you wrote is wrong. Here are a few examples:
import {
Keypair,
AccountMeta,
Connection,
PublicKey,
Transaction,
TransactionInstruction,
sendAndConfirmTransaction,
} from "#solana/web3.js";
Or
import web3 = require('#solana/web3.js');

How do I generate static HTML for my homepage from Create React App?

I have a CRA and want to have the first page generated statically to improve load time and SEO. The idea is to run a NodeJS script that renders the App document inside index.html.
Here is my code:
const { renderToString } = require("react-dom/server");
const App = require('./App');
const content = `<html><body>${renderToString(App)}</body></html>`
const fs = require('fs');
fs.writeFileSync('../public/index.html', content);
However, I have problems running it with NodeJS:
import React from 'react';
^^^^^^
SyntaxError: Cannot use import statement outside a module
Apparently, I have to build the source, but I don't know how to do it. I'm using CRA to build my React files (actually, I'm using react-app-rewired, so I could customize the build process if I only knew how to do it).
What should I do to make it work?
the error is from node: https://stackoverflow.com/a/59399717/13216797
I know it's not your question... but what about using Nextjs?
Even without a node environment you can use the "next export" command to make a bundle that will work as it would static files while still being react...
I ended up using puppeteer and just saving the generated client-side code. Before that I spent hours stubbing out first window, then window.location, then window.localStorage, window.document, window.document.createElement, it never ended. With puppeteer, it was pretty easy.

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

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

Resources