Next JS dynamic import for named export - javascript

I am learning next js. I want to call a function getItem of https://www.npmjs.com/package/encrypt-storage
Using below code, but I am getting TypeError: EncryptStorage.getItem is not a function
import dynamic from 'next/dynamic';
const EncryptStorage = dynamic(() => import('encrypt-storage').then((mod) => mod.EncryptStorage(process.env.NEXT_PUBLIC_SKK)), { ssr: false });
console.log(EncryptStorage.getItem('aa'));
please help me to sort it out.

tl;dr: You need to use await import(...) instead of dynamic(() => import(...)) as the latter is only for components.
The longer version:
This was confusing to me as well as the docs don't outright state that you can't import modules with dynamic(...), only that it should be used to import components:
React components can also be imported using dynamic imports, but in this case we use it in conjunction with next/dynamic to make sure it works just like any other React Component.
And indeed, looking at this comment from a maintainer you can't use dynamic(...) to import modules, only components.
Given this, here's a possible solution:
Also, note that .getItem(...) is a method that needs to be called on an instance of EncryptStorage.
// Needs to be ran in an `async` context or environment that supports top-level `await`s
const EncryptStorage = (await import("encrypt-storage")).default;
const encryptStorage = EncryptStorage(process.env.NEXT_PUBLIC_SKK);
console.log(encryptStorage.getItem("aa"));
And, here's a sandbox with a full working example.

Related

Importing / exporting Javascript Object Properties

I support a relatively complex legacy codebase, but am looking to modernise it a little by bringing in Webpack so that we'd have import & export capabilities in JS.
The problem I'm having is that we use a global object called App where we define and add different properties depending on the page. So for example we have the following file where we instantiate App (loaded on all pages):
app.js
const App = (() => {
const obj = {
Lib: {},
Util: {},
// etc
}
return obj;
})();
Then in another file we add to App.Lib just for the specific page that needs it:
lazyload.js
App.Lib.Lazyload = (() => {
// lazyload logic
})();
We simply concatenate the files during the bundling process, but obviously this is not ideal as none of the files have no knowledge of what goes on outside of it.
Exporting only seems to work for the top level object (where the object is defined), so anything I add to it elsewhere cannot be exported again. For example if I add export default App.Lib.Lazyload; at the end of lazyload.js and then try to import it elsewhere it will not import the Lazyload property.
Is there any way to get this to work without major refactor? If not, would you have any suggestions about the best way to handle it?
I don't think you can import Object.properties in JS. If you want to bundle specific packages (say Lazyload) for packages that need them, you might try:
//lazyload.js
export const LazyLoad = {
//lazyload logic
}
then somewhere else...
import {LazyLoad} from 'path/to/lazyload.js';
// assuming App has already been created/instantiated
App.Lib.Lazyload = LazyLoad;
Using Export Default...
//lazyload.js
const LazyLoad = {};
export default LazyLoad;
then...
import LazyLoad from 'path/to/lazyload.js';
App.Lib.LazyLoad = LazyLoad;
You can find help with Imports and Exports at MDN.

How to best import "server-only" code in Next.js?

In the getServerSideProps function of my index page, I'd like to use a function foo, imported from another local file, which is dependent on a certain Node library.
Said library can't be run in the browser, as it depends on "server-only" modules such as fs or request.
I've been using the following pattern, but would like to optimize it. Defining foo as mutable in order to have it be in scope is clunky and seems avoidable.
let foo;
if (typeof window === "undefined") {
foo = require("../clients/foo");
}
export default function Index({data}) {
...
}
export async function getServerSideProps() {
return {
props: {data: await foo()},
}
}
What would be the best practice here? Is it somehow possible to leverage ES6's dynamic import function? What about dynamically importing within getServerSideProps?
I'm using Next.js version 9.3.6.
Thanks.
UPDATE:
It seems as if Next.js's own dynamic import solution is the answer to this. I'm still testing it and will update this post accordingly, when done. The docs seem quite confusing to me as they mentionn disabling imports for SSR, but not vice versa.
https://nextjs.org/docs/advanced-features/dynamic-import
When using getServerSideProps/getStaticProps, Next.js will automatically delete any code inside those functions, and imports used exclusively by them from the client bundle. There's no risk of running server code on the browser.
However, there are a couple of considerations to take in order to ensure the code elimination works as intended.
Don't use imports meant for the server-side inside client-side code (like React components).
Ensure you don't have unused imports in those files. Next.js won't be able to tell if an import is only meant for the server, and will include it in both the server and client bundles.
You can use the Next.js Code Elimination tool to verify what gets bundled for the client-side. You'll notice that getServerSideProps/getStaticProps gets removed as do the imports used by it.
Outside of getServerSideProps/getStaticProps, I found 2 fairly similar solutions.
Rely on dead code elimination
In next.config.js:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.RUNTIME_ENV': JSON.stringify(isServer ? 'server' : 'browser'),
}),
);
export const addBreadcrumb = (...params: AddBreadcrumbParams) => {
if (process.env.RUNTIME_ENV === 'server') {
return import('./sentryServer').then(({ addBreadcrumb }) => addBreadcrumb(...params));
}
return SentryBrowser.addBreadcrumb(...params);
};
Note that some for reason I don't understand, dead code elimination does not work well if you use async await, or if you use a variable to store the result of process.env.RUNTIME_ENV === 'server'. I created a discussion in nextjs github.
Tell webpack to ignore it
In next.config.js
if (!isServer) {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /sentryServer$/,
}),
);
}
In that case you need to make sure you will never import this file in the client otherwise you would get an error at runtime.
You can import the third party library or a serverside file inside getServerSideProps or getInitialProps since these functions run on server.
In my case I am using winston logger which runs on server only so importing the config file only on server like this
export async function getServerSideProps (){
const logger = await import('../logger');
logger.info(`Info Log ->> ${JSON.stringify(err)}`);
}
You can also import library/file which has default export like this
export async function getServerSideProps(context) {
const moment = (await import('moment')).default(); //default method is to access default export
return {
date: moment.format('dddd D MMMM YYYY'),
}
}

How to share a library globally in React?

For example, there's styled-components library, what if i want to use it in 10 components, i would have to import it 10 times? Or HOC is the only way to deal with that?
I'm doing this in my ExampleComponent:
import styled from 'styled-components`;
Then i can use it, i need an example with HOC approach or something better.
... i would have to import it 10 times?
Only if the components are all defined in their own modules, and they all need to use styled-components. You have to import once per module, not once per component. There's no requirement that each component be written in its own module, doing so (or not) is a matter of style.
As Dan Abramov said:
I still get surprised that “one function per file” is clearly unnecessary but “one component per file” is somehow a common practice!
In any case, don't worry: You only have one copy of the library. The import is just binding the modules together, not actually copying the module you import from into your module.
In case you want a HOC. You can do something like:
import styled from 'styled-components`;
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
const withWrapper = component => {
return <Wrapper>{component}</Wrapper>
}
export default withWrapper;
And then use it as:
const Home = () => {
return <h1>Home</h1>
}
export default withWrapper(Home);
Hope this will help you.

how use local scope with eval

I have a module JS where i use React
import React from 'react'
My component
export default class TaskDetail extends Component {...
I have a a string that represents a code:
str=`props => {
return React.createElement(.....
and I would use this code in a module JS like this:
const MyCustomWidget = eval(str)
so that one it would be equal to write:
const MyCustomWidget = props => {
return React.createElement(.....
I use MyCustomWidget to create a custom element in react-jsonschema-form
the point of my question is:
in my module i have imported React but i have error React is not defined
that is because the result of eval have another scope...
if i write on top of my module:
window.React = React
it works! but I wouldn't want to use
It is possible use eval and use the scope of my module ? I would like to use my imported React variable in my module without use window.React=React
is possible?
If you want to experiment with it...
const evalInContext = a =>
// eslint-disable-next-line no-new-func
new Function('require', 'const React = require("react");' + a).bind(
null,
require
);
See how they evaluate and run react code from a live editor in react-styleguidist
https://github.com/styleguidist/react-styleguidist/blob/34f3c83e76/src/client/rsg-components/ReactExample/ReactExample.spec.js
Once again, if you cannot 100% trust what you eval, don't do it.

ES6 Dynamic importing with namespace?

When using dynamic imports, can I define what I want to import like regular imports?
For example:
import Person from '/classes.js'
As dynamic:
await import('Person from /classes.js') //Incorrect obviously
Dynamic imports will hand you everything from within the module. You can use destructuring the extract the pieces you want.
const { Person } = await import('/classes.js');
You can try this when you need to import some specific file.
const moduleSpecifier = '/classes.js';
import(moduleSpecifier)
.then(someModule => someModule.myFucntion());

Categories

Resources