So I have a custom private npm package which I want to dynamically imports it like so.
export default function SelectedIcon({path, ...props}: Props) {
const {data: icon} = require(`#mypackagelib/icons/${path}`)
return <Icon icon={icon} {...props} />
}
// I also tried to have it differently such as:
export default function SelectedIcon({path, ...props}: Props) {
const Component = React.lazy(() => import(`#mypackagelib/icons/${path}`))
return <Component {...props} />
}
// However, same error, so it seems like dynamically importing this is the problem
However, I got many errors, all the same message for different components in the library, from running Storybook
Module parse failed: Unexpected token (2:101)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| /// <reference types="react" />
| import { PropA, PropB } from '../../File';
> declare function NameOfAComponent(props: PropA): JSX.Element;
| declare namespace NameOfAComponent {
| var data: PropB;
The weird thing that I don't understand is that, if I import the package like the below code, then it works
import OneOfTheComponent from '#mypackagelib/icons/OneOfTheComponent'
However, I have a case that I need to use dynamic import
I have the following config tsconfig.json when I build the #mypackagelib package with tsc and ts-node
{
"compilerOptions": {
"outDir": "dist",
"target": "ES2020",
"module": "commonjs",
"jsx": "react",
"lib": ["es6", "dom", "ES2020"],
"moduleResolution": "node",
"declaration": true,
"strict": true,
"skipLibCheck": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": false,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmitHelpers": true,
"importHelpers": true,
"pretty": false,
},
"ts-node": {
"swc": true
},
"exclude": [
"node_moduls_local",
"node_modules",
"dist"
],
"include": [
"types.d.ts",
"src/icons/**/*"
]
}
My main.js under ./storybook folder looks like this:
module.exports = {
stories: [
"../src/**/*.stories.#(ts|tsx)"
],
addons: [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/addon-interactions",
"#storybook/preset-create-react-app",
{
name: 'storybook-addon-swc', // I thought adding this will works, but it doesn't
options: {
enable: true,
enableSwcLoader: true,
enableSwcMinify: true,
},
},
],
framework: "#storybook/react",
core: {
builder: 'webpack5',
},
experiments: {
topLevelAwait: true,
}
}
I think this might be webpack problem in general. What can I do to make it work? What loader do I need to add? What do my main.js file needs to look like? Why it needs a loader when I use dynamic imports but works fine if it's a normal import?
Related
I'm integrating vitest with a NextJS13 app, but running into problems with a simple test run.
Not sure what the problem is, I tried to do some tweaking with the vitest.config.ts but no luck. I tried adding the dir option, modified the include option to grab files from the source file but no luck.
I thought maybe it had to do with the tsconfig.json file, but it's still outputting the error.
This is the directory of the file
Here are the files in question:
vitest.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config'
import react from '#vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
setupFiles: 'setupTests.ts',
// dir: './src'
// includeSource: ['src/**/*.{js,ts,tsx}'],
},
});
tsconfig.json
{
"compilerOptions": {
"target": "ES2017",
"lib": ["es6", "dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "ESNEXT",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"incremental": true,
// "paths": {
// "src": ["./src/*"]
// }
},
"exclude": ["node_modules"],
"include": ["vitest.config.ts","**/*.ts", "**/*.tsx", "next-env.d.ts",
"next.config.js"]
}
DataTable.test.tsx - src/common/components/DataTable/DataTable.test.tsx
// components
import DataTable from 'src/common/components/DataTable';
// dependencies
import {describe, it} from 'vitest'
import {screen, render} from '#testing-library/react'
describe('DataTable test', () => {
it('render the app', () => {
// arrange
render(<DataTable />)
// act
const assetText = screen.getByText("asset")
// assert
// expect(assetText).toBeInTheDocument()
})
})
DataTable component - src/common/components/DataTable/DataTable.tsx
export const DataTable = () => {
return (
<div>
<h1>assets</h1>
</div>
);
};
Index.tsx - src/common/components/DataTable/index.tsx
import { DataTable } from 'src/common/components/DataTable/DataTable';
export default DataTable;
I'm new to vitest and nextjs, your help/guidance will be appreciated.
There are two things needed here to make the import DataTable from 'src/common/components/DataTable'; import work:
TypeScript needs the paths compilerOption set.
Vite needs to have the same alias set.
The "paths" compilerOption in TypeScript will need a /* on the end of the "src" key to be able to resolve paths underneath the "src" directory (see tsconfig.json reference):
{
"compilerOptions": {
"paths": {
"src/*": ["./src/*"]
}
}
}
Vite/Vitest will also need to know how to resolve "src/common/components/DataTable", and that would usually be done with the resolve.alias setting, but rather than duplicating the alias here, you could also use a plugin, like vite-tsconfig-paths, to add the path aliases if finds in relevant tsconfig.json files:
import react from "#vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default {
plugins: [tsconfigPaths(), react()],
};
I am setting up a new project with Remix (remix.run) and I am trying to configure Cypress/ESLint for component testing. I have a TestComponent.cy.ts with some boilerplate code:
describe('TestComponent.cy.ts', () => {
it('playground', () => {
// cy.mount()
})
})
However, the describe function throws this Error:
Parsing error: ESLint was configured to run on `<tsconfigRootDir>/component/TestComponent.cy.ts` using `parserOptions.project`: <tsconfigRootDir>/../../../../../../users/tduke/desktop/dev/blog/cypress/tsconfig.json
However, that TSConfig does not include this file. Either:
- Change ESLint's list of included files to not include this file
- Change that TSConfig to include this file
- Create a new TSConfig that includes this file and include it in your parserOptions.project
I have tried to reconfigure my .tsconfig.json and .eslintrc.js to no avail. Currently, this is what those files look like:
tsconfig.json:
{
"exclude": ["./cypress", "./cypress.config.ts"],
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx", "./cypress/component/*.cy.ts", "./cypress/**/*.cy.ts",
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2019"],
"types": ["vitest/globals"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "CommonJS",
"moduleResolution": "node",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
"skipLibCheck": true,
// Remix takes care of building everything in `remix build`.
"noEmit": true
}
}
.eslintrc.js:
/** #type {import('#types/eslint').Linter.BaseConfig} */
module.exports = {
extends: [
"#remix-run/eslint-config",
"#remix-run/eslint-config/node",
"#remix-run/eslint-config/jest-testing-library",
"prettier",
],
env: {
"cypress/globals": true,
},
parserOptions: {
project: './tsconfig.json'
},
plugins: ["cypress"],
// We're using vitest which has a very similar API to jest
// (so the linting plugins work nicely), but we have to
// set the jest version explicitly.
settings: {
jest: {
version: 28,
},
},
};
I also encountered the same problem, I solved the problem by removing the project property under parserOptions
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
// project: 'tsconfig.json',
tsconfigRootDir: __dirname,
},
You should add ["./.eslintrc.js"] to your tsconfig.json file:
{
"include": ["./.eslintrc.js"],
//...
}
This is what I ended up putting in my .eslintrc.json:
module.exports = {
...
parserOptions: {
parser: '#typescript-eslint/parser',
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
};
It may have been that the project needed to be ./tsconfig.json instead of tsconfig.json. My tsconfig file is in the project root.
And this brought up some more errors linting things like babel config so I created an .eslintignore file and added those in there:
.eslintrc.js
ios/
android/
*.config.js
This solved all my issues.
I am writing a React application using Vite and Typescript, with automated tests using Vitest. One of my components uses react-viewer, and looks something like this
import Viewer from "react-viewer";
import { useState } from "react";
type MyComponentProps = {
url: string
};
export const MyComponent = ({ url }: MyComponentProps) => {
const [isVisible, setVisible] = useState(false);
return (
<div>
/* Some other components above which toggle the viewer */
<Viewer
visible={isVisible}
onClose={() => setVisible(false)}
images={[{ src: url, alt: "", downloadUrl: url }]}
noNavbar
/>
</div>
);
};
This works when I run the application with the development server. However, in the automated tests, I get this error:
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
When I logged Viewer when running the tests, I got this
Object [Module] { default: [Function (anonymous)] }
But when I looked at the same log statement when running it in development, I got this
function default(A3)
It looks like in development mode, Viewer is imported as a React component (as I'd expect). But in the test environment, it's incorrectly imported as an object.
I found this issue on Vite's Github, which has a workaround. I tried following the same pattern, which works
import React from "react";
import Viewer from "react-viewer";
import ViewerProps from "react-viewer/lib/ViewerProps";
type PictureViewerComponent = (props: ViewerProps) => React.ReactPortal;
type ViewerComponent = PictureViewerComponent & {
default?: PictureViewerComponent;
};
export const PictureViewer: PictureViewerComponent =
(Viewer as ViewerComponent)?.default ?? Viewer;
But I'm a bit worried that I'll see this issues with other components that use default imports. It feels like there is an underlying issue with either my tsconfig.json or vite.config.ts.
Here is the tsconfig.json
{
"exclude": [
"node_modules",
"build",
"coverage",
"storybook-static",
"public"
],
"compilerOptions": {
"types": ["vite/client", "vitest/globals"],
"paths": {
"#acme/*": ["./src/*"]
},
"allowJs": true,
"baseUrl": ".",
"typeRoots": ["./node_modules/#types"],
"target": "ES6",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "ES6",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noImplicitAny": true,
"noEmit": true,
"jsx": "react-jsx"
}
}
And here is the vite.config.ts
/// <reference types="vitest" />
import * as path from "path";
import react from "#vitejs/plugin-react";
import { defineConfig, splitVendorChunkPlugin } from "vite";
import eslint from "vite-plugin-eslint";
const config = () => {
return defineConfig({
base: "/acme/",
plugins: [react(), eslint(), splitVendorChunkPlugin()],
resolve: {
alias: [
{
find: /~(.+)/,
replacement: path.join(process.cwd(), "node_modules/$1")
},
{
find: /#acme\//,
replacement: path.join(process.cwd(), "./src") + "/"
},
{
// Workaround due to https://github.com/vitejs/vite/issues/9200
find: "path",
replacement: "path-browserify"
}
]
},
server: {
host: "0.0.0.0",
port: 80,
base: "/"
},
esbuild: {
loader: "tsx",
logOverride: { "this-is-undefined-in-esm": "silent" }
},
build: {
outDir: "build"
},
test: {
globals: true,
environment: "jsdom",
mockReset: true,
restoreMocks: true,
clearMocks: true,
setupFiles: "./src/setupTests.js",
coverage: {
reporter: ["text", "html"],
exclude: [
"node_modules/",
"src/setupTests.js",
"**/{__tests__,__stories__,test,storybook}/"
]
}
}
});
};
export default config;
I tried toggling the moduleResolution, esModuleInterop, and isolatedModules options in tsconfig.json, without success. I also tried setting test.deps.interopDefault to false in vite.config.ts, also without success.
I also tried importing the component like this, also without success
import { default as Viewer } from "react-viewer";
Any help would be appreciated! Thank you in advance
I export const FontXLarge = 18; in ThemeFont.ts.
When I want to use it, I used to use import {FontXLarge} from '../theme/ThemeFont';
After I add
{
"name": "theme"
}
I can use import {FontXLarge} from 'theme/ThemeFont'; but I can't link to the folder.
So I think that if I declare module 'ThemeFont' I can link it. And the fact that I did it. When I ctrl + click (or alt + click in VSCode, depending on your setting), it can open the file ThemeFont.ts when I use import {FontXLarge} from 'ThemeFont';
declare module 'ThemeFont' {
export const FontXLarge = 18;
}
When hover, it will show like that
But it shows error when build: Unable to resolve module ThemeFont in node_modules
We can use this library to declare local component https://github.com/tleunen/babel-plugin-module-resolver
My config for it:
create/edit: tsconfig.json
{
"compilerOptions": {
"skipLibCheck": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"jsx": "react",
"lib": ["es6"],
"moduleResolution": "node",
"noEmit": true,
"strict": true,
"target": "esnext",
"baseUrl": "./app",
"paths": {
"#/*": ["./*"]
},
"outDir": "../dst/",
"typeRoots": ["node_modules/#types"]
},
"lib": ["es2016"],
"exclude": [
"node_modules",
"babel.config.js",
"metro.config.js",
"jest.config.js"
]
}
create/edit jsconfig.json
{
"compilerOptions": {
"paths": {
"#/*": [
"./app/*"
]
}
}
}
create/edit jsconfig.js
System.config({
paths: {
'#/*': './app/*',
},
})
edit babel.config.js
const presets = ['module:metro-react-native-babel-preset'];
const plugins = [
[
'react-native-reanimated/plugin',
// {
// globals: ['__scanQRCodes', '__decode'],
// },
],
[
'module-resolver',
{
root: ['./app'],
extensions: ['.js', '.json'],
alias: {
'#': './app',
},
},
],
];
const env = {
production: {
plugins: ['react-native-paper/babel'],
},
};
module.exports = {
presets,
plugins,
env,
};
Now you can use
import {FontXLarge} from '#/Theme/ThemeFont';
remember that after config it, resetting app cache:
yarn start --reset-cache
I've installed a package called mini-alert which doesn't have its corresponding types. I've modified the tsconfig.json to declaration but when I try to use it my code it throws this error: `Uncaught TypeError: Object(...) is not a function.
1. Handling the missing types myself
interface Props{
overflow?: boolean;
autoremove?: boolean;
time?: number;
size?: string;
cartoon?: boolean;
limit?: number;
text?: string;
}
declare module 'mini-alert'{
export const miniAlert: (args: Props) => void;
};
tsconfi.json setup
{
"compilerOptions": {
"allowJs": true,
"declaration": true,
"allowSyntheticDefaultImports": true,
"baseUrl": "./",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"jsx": "react-jsx",
"lib": [
"dom",
"dom.iterable",
"esnext",
"ES2021"
],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2021",
"types": []
},
"include": [
"src",
"types"
],
"xexclude": [
"node_modules",
"./node_modules",
"./node_modules/*/*.d.ts",
"./node_modules/#types/node/index.d.ts",
]
}
Using the package in my code
const onCopyText = () => {
setIsCopied(true);
miniAlert({
overflow: true, // <-- disable behind the alert
autoremove: true, // <-- automatic remove
time: 500, // <-- milliseconds
size: 'large', // <-- small, medium, large
cartoon: false, // <-- "cartoon effect" true/false
limit: 1, // <-- max alerts visible at the same time
text: 'Copied!'
});
setTimeout(() => {
setIsCopied(false);
}, 1000);
Expected resul
I want to see a mini alert popup just like in this mini alert
Actual result
When I click a button, I don't see any alert but this in the Chrome dev tools. What am I doing wrong
My use case's in a code sandbox
Codesandbox demo
In your code sandbox you are importing mini-alert like this:
import { miniAlert } from "mini-alert";
But the documentation says to import it like this:
import miniAlert from 'mini-alert';
That means that the value you want is the default export from the package, and not a named export.
Which means the types should be something like:
declare module "mini-alert" {
const miniAlert: (args: Props) => void;
export default miniAlert;
}
I use Vue.js rather than React, so not sure of the exact implementation, but typically if you are using a package that is JavaScript and you want to use it as TypeScript, you need to create a .d.ts file to translate the package. See the TypeScript handbook