Why can I not use a dynamic import when using Nuxt3 - javascript

I am trying to create a component that will let me pass in the name of the icon I am looking for and import that icon dynamically via unplugin-icons, it seems to be not working whenever the path for the import is dynamic. If I were to just type a string it works fine. Any suggestions? I am using Nuxt 3 and Vite.
interface Icon {
name: string;
}
const props = defineProps<Icon>();
const sun = "sun";
const icon = await import(`~icons/fa-solid/${sun}`);
const PropIcon = defineComponent(icon.default);
Below is the error I recieve
const props = __props;
12 | const sun = "sun";
13 | const icon = ([__temp, __restore] = _withAsyncContext(() => import(`~icons/fa-solid/${sun}`)), __temp = await __temp, __restore(), __temp);
| ^
14 | const PropIcon = defineComponent(icon.default);
15 | const __returned__ = { props, sun, icon, PropIcon };
The above dynamic import cannot be analyzed by vite.
See https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations for supported dynamic import formats. If this is intended to be left as-is, you can use the /* #vite-ignore */ comment inside the import() call to suppress this warning.
I tried to use the /* vite ignore */ but it did not work
Below is the hardcoded version which works
interface Icon {
name: string;
}
const props = defineProps<Icon>();
const sun = "sun";
const icon = await import(`~icons/fa-solid/sun`);
const PropIcon = defineComponent(icon.default);
Below is the standard import
import IconSun from "~icons/fa-solid/sun";

Well vite already give you the answer just read it:
If this is intended to be left as-is, you can use the /* #vite-ignore
*/ comment inside the import() call to suppress this warning.
Vite cannot analyze the importing module because it cant know the value of sun
You need to surpress this error if this is intended:
const icon = await import( /* #vite-ignore */` ~icons/fa-solid/${sun}`);

Related

ReScript, TypeScript: Cannot find module '#rescript/react/src/ReactDOM.gen' or its corresponding type declarations

I am gradually rewriting an application with React to TypeScript into ReScript.
I've already implemented few components in ReScript, but this is the first one, where I use ReactDOM.Style.t as property to my component.
Here is my minimized component code:
#genType
#react.component
let make = (~sx: option<ReactDOM.Style.t>=?) => {
<div></div>
}
ReScript compiles fine (except a warning that sx is not used, but we can ignore it).
I have the following bs.js generated, which seems to be fine:
// Generated by ReScript, PLEASE EDIT WITH CARE
import * as React from "react";
function InhypedIcon(Props) {
return React.createElement("div", undefined);
}
var make = InhypedIcon;
export {
make ,
}
/* react Not a pure module */
And the following corresponding .gen.tsx file, which causes the problem:
/* TypeScript file generated from InhypedIcon.res by genType. */
/* eslint-disable import/first */
import * as React from 'react';
// #ts-ignore: Implicit any on import
import * as InhypedIconBS__Es6Import from './InhypedIcon.bs';
const InhypedIconBS: any = InhypedIconBS__Es6Import;
import type {Style_t as ReactDOM_Style_t} from '#rescript/react/src/ReactDOM.gen';
// tslint:disable-next-line:interface-over-type-literal
export type Props = { readonly sx?: ReactDOM_Style_t };
export const make: React.ComponentType<{ readonly sx?: ReactDOM_Style_t }> = InhypedIconBS.make;
This does not compile, I am getting TypeScript error:
TypeScript error in /app/src/icons/InhypedIcon.gen.tsx(11,48):
Cannot find module '#rescript/react/src/ReactDOM.gen' or its corresponding type declarations. TS2307
9 | const InhypedIconBS: any = InhypedIconBS__Es6Import;
10 |
> 11 | import type {Style_t as ReactDOM_Style_t} from '#rescript/react/src/ReactDOM.gen';
| ^
12 |
13 | // tslint:disable-next-line:interface-over-type-literal
14 | export type Props = { readonly sx?: ReactDOM_Style_t };
I do understand, that TS can not find rescript/react/src/ReactDOM.gen, however I am not really know why.
Any ideas how this can be fixed?
My package versions:
node: v16.6.2
"typescript": "^4.1.2"
"rescript": "^9.1.4"
"#rescript/react": "^0.10.3"
Thank you.
This is because rescript bindings to react do not come with GenType, so in case you want it, you have to type it yourself (cf the docs to import types):
#genType.import(("react", "CSSProperties"))
type reactStyle = ReactDOM.Style.t
#genType
#react.component
let make = (~sx: option<reactStyle>=?) => {
<div></div>
}
This should work.

React: Import svg as component dynamically

In my application I got 8 svg files which I want to import dynamically. I have tried the following:
First I created a Floor component that has one name prop. This is the name of the floor that the user wants to see. In my dynamic import, the path to the svg is specified but it always seems to return undefined.
I know the path to the svg is correct because if I fill in a wrong path, React complains it can't find the svg file.
import React, { useEffect, useState } from "react";
const Floor = ({ name }) => {
const [floor, setFloor] = useState(null);
useEffect(() => {
/*
This is where it goes wrong. If I log the result It shows 'undefined'.
*/
import(`./floors/${name}.svg`)
.then((module) => {
setFloor(module);
})
.catch((error) => {
console.error(`Icon with name: ${name} not found!`);
});
}, [name]);
const renderFloor = () => {
if (!floor) return null;
const Floor = floor.ReactComponent;
return <Floor />;
};
return <>{renderFloor()}</>;
}
export default Floor;
And this is how I import the Floor
import Floor from './Floor'
const Floorplan = () => {
return (
<Floor name="floor-4" />
)
}
export default Floorplan;
I have found something similar but I still have the same error 'undefined'
These are the errors Im getting.
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
And if I log the dynamic import response:
Object { loaded: Getter, id: Getter, exports: undefined, webpackPolyfill: 1 }
Icon.js:14
The below worked for me. I had my svgs in a folder inside src, and was using the companyName prop for the svg filename. I also used a regex to format companyName to all lower case and no space (like the svg filenames)
const [iconUrl, setIconUrl] = useState(false)
const { companyName } = job.companyByCompany
useEffect(() => {
iconFetch(companyName.toLowerCase().replace(/\s+/g, ''))
}, [companyName])
const iconFetch = async (iconName) => {
await import(`../assets/logos/${iconName}.svg`)
.then(data => setIconUrl(data.default))
.catch(err => console.log(err))
}
I could then access the svg's via the src attribute in an img tag.
<img src={iconUrl} alt={`${companyName} svg`}/>
I believe the problem is webpack cannot determine what your ${name}.svg will look like in runtime and does not include them inside any chunk. To force this behavior you should include some magic comments inside your dynamic import. Something like:
...
useEffect(() => {
import(
/* webpackInclude: /\.svg$/ */
/* webpackChunkName: "svg-imgs-chunk" */
/* webpackMode: "lazy" */
`./floors/${name}.svg`)
.then((module) => {
setFloor(module);
})
...

using react-intl to translate a message key outside a component

I'm using the react-intl library for internationalization. Inside a component, I use the injectIntl HOC to translate message keys:
import {injectIntl} from 'react-intl';
const Component = props => (
const message = props.intl.formatMessage({id: 'message.key'});
// remainder of component omitted
);
export default injectIntl(Component);
Is it possible to get a message translation if I'm not inside a component?
Yes it is!
You have to setup you application to provide the intl object so that you can use it from outside react components. You will have to use the imperative API for these cases. You can do something like this:
import { IntlProvider, addLocaleData, defineMessages } from 'react-intl';
import localeDataDE from 'react-intl/locale-data/de';
import localeDataEN from 'react-intl/locale-data/en';
import Locale from '../../../../utils/locale';
addLocaleData([...localeDataEN, ...localeDataDE]);
const locale = Locale.getLocale(); // returns 'en' or 'de' in my case
const intlProvider = new IntlProvider({ locale, messages });
const { intl } = intlProvider.getChildContext();
const messages = defineMessages({
foo: {
id: 'bar',
defaultMessage: 'some label'
}
});
const Component = () => (
const componentMessage = intl.formatMessage(messages.foo);
);
I've done a different setup for me, but I guess this should work for you.

Customize the way “import” works with webpack

I need to customize the way webpack is handling imports in my app.
Some of my services have mock implementations.
In test mode, I want to import mock file instead of real service if a file with ‘.mock’ postfix exists next to the service, otherwise imports the service itself.
Please note that I need different output files (‘main.js’ and ‘test.js’). So I need to make sure test.js is not including the real services implementations (preventing execution is not enough, the source should not be imported at all).
Services folder contains following files:
service-1.js
service-1.mock.js
service-2.js
index.js
services/index.js:
import service1 from ‘./service-1’
import service2 from ‘./service-2’
export {service1, service2}
Please advise how can I config my webpack.
According to the comments I can suggest these workarounds using custom loader:
Method #1
Create one .mobile and .desktop for each of your components along the main file (e.g: component.js, component.mobile.js, component.desktop.js) and use this custom loader:
const targets = {
desktop: 'desktop',
mobile: 'mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import Header from './shared/Header';
import Footer from './shared/about';
import Categories from './category/categories';
// rest of code
`;
const manipulated = manipulateSource(source, targets.mobile, ['components', 'shared']);
console.log(manipulated);
function manipulateSource(src, target = targets.desktop, pathMatches = []) {
const paths = pathMatches.length ? `(${pathMatches.join('|')})` : '';
const pattern = new RegExp(`(?<left>.*import.*${paths}.*\\\/)(?<name>[\\w\\-_]*)(?<rest>.*\\n)`, 'g');
const manipulated = src.replace(pattern, (...args) => {
const [{
left,
name,
rest
}] = args.slice(-1);
return `${left}${name}.${target}${rest}`;
});
return manipulated;
}
Method #2
For those files has different implementations for .mobile and .desktop create the third file (or fourth in case you want to put shareable code in main file) with same name and a meaningful extension (e.g: component.platformAdaptive.js) which can be handled with regular expresion (or any other way of manipulation). In this method you may need to put basic implementation in last file in case you use strongTypes (e.g: Typescript):
const targets = {
desktop: 'desktop',
mobile: 'mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import Header from './shared/Header.platformAdaptive';
import Footer from './shared/about.platformAdaptive';
import Categories from './category/categories.platformAdaptive';
// rest of code
`;
const manipulatedMob = manipulateSource(source, 'platformAdaptive', targets.mobile);
const manipulatedDesk = manipulateSource(source, 'platformAdaptive');
console.log(manipulatedMob);
console.log(manipulatedDesk);
function manipulateSource(src, replace, target = targets.desktop) {
const pattern = new RegExp(`(?<left>.*\\\/)(?<name>[\\w\\-_]*\.)${replace}(?<rest>.*\\n)`, 'g');
const manipulated = src.replace(pattern, (...args) => {
const [{
left,
name,
rest
}] = args.slice(-1);
return `${left}${name}${target}${rest}`;
});
return manipulated;
}
Both of the above methods come with some restrictions in imports, like you can't use Barrel files (index.js) since they assume last chunk of the import is the component file.
In this case you can add multiple folders with a barrel to handle those imports. for example in second method you'll need such structure:
|-- components.platformAdaptive
|-- index.js
|-- components.mobile
|-- index.js
|-- components.desktop
|-- index.js
Or You can use / instead of . to create a nested structure (e.g: components/platformAdaptive):
|-- components
|-- [+] platformAdaptive
|-- [+] mobile
|-- [+] desktop
Method #3
Another way to handle this situation would be to have different classes with different names. For example, a List component with different implementations for mobile and desktop then there would be three components like ListPlatformAdaptive, ListMobile, ListDesktop - in which the ListPlatformAdaptive may has the basic implementations - and a barrel in component folder which exports the components:
import * as ListPlatformAdaptive from './list.platformAdaptive';
import * as ListMobile from './list.mobile';
import * as ListDesktop from './list.desktop';
export {
ListPlatformAdaptive,
ListMobile,
ListDesktop
}
The structure would be like this:
|-- components
|-- list.platformAdaptive.js
|-- list.mobile.js
|-- list.desktop.js
|-- index.js
Then manipulation would be like this:
const targets = {
desktop: 'Desktop',
mobile: 'Mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import HeaderPlatformAdaptive as Header from './shared/Header';
import FooterPlatformAdaptive as Footer from './shared/about';
import CategoriesPlatformAdaptive as Categories from './category/categories';
// rest of code
`;
const replace = 'PlatformAdaptive';
const manipulatedMob = manipulateSource(source, replace, targets.mobile);
const manipulatedDesk = manipulateSource(source, replace);
console.log(manipulatedMob);
console.log(manipulatedDesk);
function manipulateSource(src, replace, target = targets.desktop) {
const pattern = new RegExp(replace, 'g');
const manipulated = src.replace(pattern, target);
return manipulated;
}
I this method you should be careful about the barrel files to be excluded and the downside of this method is that all of the components have been imported already therefore import cost is not acceptable.
Method #4
Another way I can think of is to add some notes as comment and react againts its existance in that line:
const targets = {
desktop: 'Desktop',
mobile: 'Mobile'
};
const source = `
import Home from './components/home';
import About from './components/about';
import Header from './shared/Header'; /* #adaptive */
import Footer from './shared/about'; /* #adaptive: Desktop */
import Categories from './category/categories'; /* #adaptive: Mobile */
// rest of code
`;
const manipulatedMob = manipulateSource(source, targets.mobile);
const manipulatedDesk = manipulateSource(source);
console.log(manipulatedMob);
console.log(manipulatedDesk);
function manipulateSource(src, targetDevice = targets.desktop) {
const pattern = /(?<left>.*import\s+)(?<name>\w+)(?<rest1>.*)\#adaptive(\:\s*(?<target>\w+))?(?<rest2>.*)/g
const manipulated = src.replace(pattern, (matched, ...args) => {
let [{
left,
name,
rest1,
target,
rest2
}] = args.slice(-1);
target = target || targetDevice;
return target == targetDevice ?
`${left}${name}${target}$ as ${name}${rest1}${rest2}` :
matched;
});
return manipulated;
}
In this method like method #2 imported components names are different than the original but mapped to original name which is not good at all, but I like it the most since if using it in barrel files and it's possible to change imported file address. Another fun part can be to pass target files address with respect to target device and parse it.
Conclusion
As you can see all of my answers were about handle sources without checking the existence of the file and assumes the developer is sure about it. BTW, you can search to see if there is anyway to find file absolute path and then check availability of targeted substitutes.

Is it possible to include feature that depend on if the user as installed a certain package (optionalFeature)

I am writing a module and I want that if an user have installed in his project react-intl, to export an advanced component with translation feature.
This way I don't have to maintain two version of my components to avoid installation warning.
I have been trying to play with optionalDepenceny, but they are installed on the project of the user when he install my package.
Typically, this is the source I am trying to import
/**
*
* ToggleOption
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import Option from 'bootstrap-styled/lib/Option';
let injectIntl;
let intlShape;
// this make react-intl optional to our component and our module
try {
const reactIntl = require('react-intl'); // eslint-disable-line
injectIntl = reactIntl.injectIntl; // eslint-disable-line
intlShape = reactIntl.intlShape; // eslint-disable-line
} catch (er) {
injectIntl = null;
intlShape = null;
}
/**
* This component is automatically used when using `<Toggle />`
* If you need a different option tag, instead just pass the prop `optionTag` of `<Toggle />`.
*/
const ToggleOption = ({
tag: Tag,
value,
message,
intl,
}) => (
<Tag value={value}>
{message && intl ? intl.formatMessage(message) : value}
</Tag>
);
ToggleOption.defaultProps = {
tag: Option,
};
/* eslint-disable react/require-default-props */
ToggleOption.propTypes = {
/**
* Replace the default component tag by the one specified. Can be:
*/
tag: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
PropTypes.func,
]),
/**
* The value
*/
value: PropTypes.string.isRequired,
/**
* react-intl messages (optional)
*/
message: PropTypes.object,
};
let exported = ToggleOption; // eslint-disable-line import/no-mutable-exports
if (intlShape) {
/** #ignore */
ToggleOption.propTypes.intl = intlShape.isRequired;
exported = injectIntl(ToggleOption);
}
export default exported;
Is there a way to configure my module in order to do that ?
Optional dependencies in your package.json are always installed as default. To avoid them you have to install with:
npm install [PACKAGE] --no-optional
You can note it, for example, in your description, like I do: https://github.com/Llorx/updated-require
PS: Your code is fine.
You can check if a module was already included / required by testing if it's module (it's "namespace") was define (if (typeof Module !== "undefined")...).
Another option, if you want to include / require a module that might have been installed is to surround the include / require statement with a try / catch.
Good luck!
EDIT:
As for using optionalDependencies, it won't change your code - you will still have to perform the same checks using try/catch.

Categories

Resources