I am trying to make a React library using Typescript, Webpack and Babel however I am running into a problem. If I build then import the library into a React project then my import is 'undefined' (See the below error). I think this would be because in the bundle.js there is no module.exports for the variable that would represent my class there is only a __webpack_exports__["default"] = (ExampleComponent); (However I am unsure of what this does in practice so I could be wrong.)
I specifically got this error:
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.
What I have tried:
Changing the tsconfig target to es6 and module to commonjs
Changing the tsconfig target to es6 and module to esnext
Changing the tsconfig target to es5 and module to esnext
Changing the tsconfig target to esnext and module to esnext
Changing the tsconfig target to es6 and module to es6
Changing the tsconfig target to commonjs and module to es6
Importing the library as 'import * as ExampleComponent from ...' and 'import {ExampleComponent} from ...' (As intended) both times 'ExampleComponent' was undefined.
Versions:
babel-loader: ^8.1.0
Webpack: ^4.43.0
typescript: ^3.8.3
Code:
React Project:
import React from "react";
import { ExampleComponent } from "test-lib";
// This is always undefined
console.log(ExampleComponent);
function App() {
return <ExampleComponent />;
}
export default App;
Library Project:
index.ts:
import ExampleComponent from './ExampleComponent'
export { ExampleComponent }
ExampleComponent.tsx
import * as React from 'react'
import './ExampleComponent.css'
interface Props {
text: string
}
// prettier-ignore
const ExampleComponent: React.FC<Props> = ({ text }) => (
<h1 className="example-text">{text}</h1>
)
export default ExampleComponent
Library Configs:
tsconfig.json:
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src", "tests"],
"exclude": ["node_modules", "dist", "example"]
}
.babelrc:
{
"presets": [
[
"#babel/preset-env",
{
"debug": true,
"useBuiltIns": "usage",
"corejs": 3
}
],
"#babel/preset-react",
"#babel/preset-typescript"
]
}
Webpack Config:
const path = require('path')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
module.exports = {
entry: {
bundle: './src/index.ts',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.json'],
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [{ loader: 'babel-loader' }, { loader: 'ts-loader' }],
},
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader'],
},
{
test: /\.(gif|png|jpe?g|svg)$/,
use: [
'file-loader',
{
loader: 'image-webpack-loader',
options: {
disable: true,
},
},
],
},
{
test: /\.js$/,
enforce: 'pre',
loader: 'source-map-loader',
},
],
},
plugins: [new ForkTsCheckerWebpackPlugin()],
}
If you want to see the full code here is a link to the Github Repo.
Based on the comment from Scovy I was able to get this working by using the output.libraryTarget and output.globalObject output options.
Now my output entry in my webpack.base.config.js looks like this:
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
libraryTarget: 'umd',
globalObject: 'this',
},
Update:
The above change did not seam to work 100% of the time so I found a library called esm-webpack-plugin which ended up working perfectly.
So the final code for the output entry in the webpack config is:
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
library: 'LIB',
libraryTarget: 'var',
},
and I also added the plugin:
plugins: [new ForkTsCheckerWebpackPlugin(), new EsmWebpackPlugin()],
Related
I created a UI Library with CRA.
The library is published on npm.
Components in the library use SVG, which I use with the syntax of
import {ReactCompoent as Icon} from 'assets / icon'
I want to use the library inside my main app that I create with react 17.0.2 and webpack 5.
I add the loaders to Webpack and I can use svg as components inside Main app
as -
import CalendarIcon from './Calendar.svg'
and it works.
After I install the library and import the component that use SVG as ReactComponent:
import {BadgeBox} from '#privatelib/ui/lib/src'
the app crashes. With an error of:
WARNING in ./node_modules/#privatelib/ui/lib/src/components/BadgeBox/index.js 107:1697-1707
export 'ReactComponent' (imported as 'CloseIcon') was not found in '../../assets/sm/xsmall-icon.svg' (possible exports: default)
My React UI Library:
components:
import { ReactComponent as CloseIcon } from '../../assets/sm/x-small-icon.svg';
tsconfig file:
{
"compilerOptions": {
"target": "es5",
"baseUrl": "src",
"lib": [
"dom",
"dom.iterable",
"esnext",
"es5",
"es2015",
"es2016"
],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"sourceMap": true,
"declarationMap": true,
"declaration": true,
"outDir": "lib"
},
"types": ["cypress", "cypress-file-upload"],
"include": [
"src/**/*",
"custom.d.ts",
],
}
My Main App:
webpack config:
const HtmlWebPackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;
module.exports = {
output: {
filename: '[name].[contenthash:20].esm.js',
chunkFilename: '[name].[chunkhash:20].esm.js',
hashFunction: 'xxhash64',
pathinfo: false,
crossOriginLoading: false,
clean: true,
publicPath: 'auto',
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '.json'],
},
devServer: {
port: 3011,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.m?js/,
type: 'javascript/auto',
resolve: {
fullySpecified: false,
},
},
{
test: /\.(css|s[ac]ss)$/i,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['#svgr/webpack'],
},
{
test: /\.(?:ico|gif|png|jpg|jpeg|)$/i,
type: 'asset/resource',
},
{
test: /\.(woff(2)?|eot|ttf|otf|)$/,
type: 'asset/inline',
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'sidebar',
filename: 'remoteEntry.js',
remotes: {},
exposes: {
'./SideBar': './src/components/Sidebar/index.tsx',
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
},
},
}),
new HtmlWebPackPlugin({
template: './src/index.html',
}),
],
};
babelrc:
{
"presets": ["#babel/preset-typescript", "#babel/preset-react", "#babel/preset-env"],
"plugins": [
["#babel/transform-runtime"],
"babel-plugin-styled-components"
]
}
TL;DR
The right way of importing default exports in JavaScript is
import CloseIcon from '../../assets/sm/x-small-icon.svg';
In JavaScript, the import syntax is as follows:
import defaultExport from "module-name";
import { export1 } from "module-name";
which means:
"import the component of module-name that is exported as default and use it as defaultExport"
"import the component of module-name that is exported as export1 and use it as such"
According to the SVGR docs (1),
Please note that by default, #svgr/webpack will try to export the React Component via default export if there is no other loader handling svg files with default export.
Translated to plain JavaScript-English, this means "as long as you do not tell us otherwise, we will export the ReactComponent as the default". Since you are not configuring anything different in your webpack config and trying to import the ReactComponent, I assume it should be available as the default export. Therefore, the right way to importing it is
import CloseIcon from '../../assets/sm/x-small-icon.svg';
This is just a guess, of course you can proof me wrong. (2)
I assume you are using #svgr/webpack again, as in your previous question.
Since this proposal is deducted from the docs and your code, there might be other factors involved that I'm not aware of.
I have a React Library that I want to make buildable using Webpack. The library is written using Typescript. It seems everything is working, but for some reason enums aren't.
When I install the library into my React App, I find Cannot read properties of undefined in the browser console for the enum.
I've tried looking in the outputted bundle and it does appear that the enum is being compiled into the bundle. I feel like I must have a misconfiguration somewhere. Before I was able to bundle with microbundle-crl which we're moving from in favour of Webpack.
Here is my webpack.config.js file:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const DtsBundleWebpack = require('dts-bundle-webpack');
module.exports = {
mode: 'production',
entry: './src/index.tsx',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
resolve: {
extensions: ['.tsx', '.ts', '.json', 'jsx', '.js'],
},
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, //Extract css into files
'css-loader', // Turns css into commonjs
],
},
{
test: /\.pcss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader', // Turns css into commonjs
'postcss-loader'
]
},
],
},
plugins: [
new MiniCssExtractPlugin({ filename: 'randomcompany-components.css' }),
new DtsBundleWebpack({
name: '#randomcompany/components',
main: './dist/src/index.d.ts',
out: '../index.d.ts',
}),
],
};
My tsconfig.json is as follows:
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": ["dom", "esnext"],
"types": ["react", "react-scripts"],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"declaration": true,
"declarationDir": "dist",
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"isolatedModules": true,
"preserveConstEnums": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"include": ["src", "src/lang"],
"exclude": ["node_modules", "dist", "src/stories"]
}
My enum is in this format:
export enum Test {
TestOne = "Test One",
TestTwo = "Test Two",
TestThree = "Test Three"
};
export default Test;
You should write it like below:
const Test = {
TestOne: "Test One",
TestTwo: "Test Two",
TestThree: "Test Three",
};
export default Test;
But in my opinion, using enum makes your bundle file a little bit heavy, just advise. use simple JavaScript objects.
I'm trying to setup up absolute imports, but it seems my webpack configs are wrong and the entry point it starts from is relative and not from ./src
I've looked through the webpack docs and they recommend adding an entry point, which I have but still nothing.
Error I get:
Module not found: Error: Can't resolve 'contexts' in '/Users/gurneet/Desktop/Projects/projectfrontend/src/components/Home'
structure:
Project
|- src
| - components
| - Home
| - Featured.tsx
| - contexts
| - index.ts
| - AllDataProvider.tsx
|-package.json
|-tsconfig.json
|-webpack.config.js
// FEATURED.TSX
import React from 'react';
import {useData} from '../../contexts'; // this works
import {useData} from 'contexts'; // this doesn't work.
Even though when I hover over both the imports they points to the say index.ts file that has all the exports.
// WEBPACK
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
devtool: 'source-map',
devServer: {
historyApiFallback: true,
},
entry: './src',
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader',
],
},
{
test: /\.svg/,
use: {
loader: 'svg-url-loader',
options: {
// make all svg images to work in IE
iesafe: true,
},
},
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
}),
new CopyWebpackPlugin({
patterns: [
{ from: 'public/_redirects' },
],
}),
],
};
//TSCONFIG
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"baseUrl": "src",
"paths": {
"src/*": ["src/*"]
},
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": false,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
Where am i going wrong?
When I'm trying to bundle my React project, I get the following error regarding an image I'm about to load:
Oddly enough, when I hide the error overlay, I can see in a browser that my picture has been loaded correctly, despite Webpack's complaints. I've already been carefully following other working config examples, where that problem doesn't occur, but I can't easily identify the source of the issue, what leaves me here with no idea how to approach it or where to look for a hint. I've also tried different loaders: url-loader, file-loader and image-webpack-loader, but to no avail. Maybe that's Typescript what causes these troubles?
App.tsx where I'm importing my image:
import "./app.d";
import React from "react";
import SampleImage from "./assets/images/sample-image.jpg";
const App: React.FC = () => (
<div>
<img src={SampleImage} />
</div>
);
export default App;
app.d.ts with modules declarations to allow importing assets in typescripts files:
declare module "*.jpeg";
declare module "*.jpg";
declare module "*.jpeg";
declare module "*.png";
webpack.common.ts - my main Webpack config file, where I placed both url-loader and file-loader as it is in ejected create-react-app config, but it doesn't really change anything:
import path from "path";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";
import { CleanWebpackPlugin } from "clean-webpack-plugin";
module.exports = {
entry: path.resolve(__dirname, "..", "./src/index.tsx"),
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve("babel-loader"),
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript",
],
},
},
],
},
{
exclude: [
/\.html$/,
/\.(js|jsx)$/,
/\.(ts|tsx)$/,
/\.css$/,
/\.json$/,
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/,
],
loader: require.resolve("file-loader"),
options: {
name: "static/media/[name].[hash:8].[ext]",
},
include: path.resolve(__dirname, "..", "./src/assets"),
},
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve("url-loader"),
options: {
name: "static/media/[name].[hash:8].[ext]",
},
include: path.resolve(__dirname, "..", "./src/assets"),
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".js", ".jsx"],
},
output: {
path: path.resolve(__dirname, "..", "./dist"),
filename: "bundle.js",
},
devServer: {
contentBase: path.resolve(__dirname, "..", "./dist"),
hot: true,
compress: true,
open: true,
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "React App",
template: path.resolve(__dirname, "..", "./src/index.html"),
}),
new ForkTsCheckerWebpackPlugin({
async: false,
eslint: {
files: path.resolve(__dirname, "..", "./src/**/*"),
},
}),
],
};
webpack.dev.ts
import webpack from "webpack";
import ReactRefreshWebpackPlugin from "#pmmmwh/react-refresh-webpack-plugin";
module.exports = {
mode: "development",
devtool: "eval-source-map",
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve("babel-loader"),
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript",
],
plugins: [require.resolve("react-refresh/babel")],
},
},
],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin({
overlay: {
sockIntegration: "wds",
},
}),
],
};
webpack.prod.ts
import webpack from "webpack";
module.exports = {
mode: "production",
devtool: "source-map",
};
webpack.config.ts
import merge from "webpack-merge";
import devConfig = require("./webpack.dev");
import prodConfig = require("./webpack.prod");
import commonConfig = require("./webpack.common");
module.exports = ({ env }: { env: "dev" | "prod" }) => {
const envConfig = env === "dev" ? devConfig : prodConfig;
return merge(commonConfig, envConfig);
};
Additionaly, my .babelrc file:
{
"presets": [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript"
],
"plugins": [
"react-refresh/babel",
[
"#babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}
and tsconfig.json:
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src"]
}
Scripts in package.json:
"scripts": {
"start": "webpack serve --config config/webpack.config.ts --env env=dev --hot",
"build": "webpack --config config/webpack.config.ts --env env=prod",
},
I'd be really grateful for any hint how to solve the issue.
Thanks to help received here: https://github.com/webpack/webpack/issues/12276#event-4152056913, I've managed to surpass the error overlay by changing the options passed to the ForkTsCheckerWebpackPlugin in my webpack.common.ts file.
Before:
new ForkTsCheckerWebpackPlugin({
async: false,
eslint: {
files: path.resolve(__dirname, "..", "./src/**/*"),
},
}),
Now:
new ForkTsCheckerWebpackPlugin({
async: false,
eslint: {
files: path.resolve(__dirname, "..", "./src/**/*.{ts,tsx,js,jsx}"),
},
}),
I'm doing this:
import { GlideRecord } from "#nuvolo/servicenow-types/server/GlideRecord";
I have a file node_modules/#nuvolo/servicenow-types/server/GlideRecord.d.ts
It contains:
...
type GlideRecordConstructor = { new <T>(table: string): GlideRecord<T> };
type GlideRecord<T> = GlideRecordBase<T> & T;
declare const GlideRecord: GlideRecordConstructor;
export { GlideRecord };
If I run tsc it works, no errors
Webpack says:
Module not found: Error: Can't resolve '#nuvolo/servicenow-types/server/GlideRecord' in ...
Webpack config:
{
mode: "production",
//set name of record as the library name
output: {
filename: "file.js"
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
//use: 'awesome-typescript-loader',
exclude: /node_modules/,
},
],
},
resolve: {
//modules: ['node_modules'],
extensions: [ '.ts', '.js' ], // Allows imports to work from .ts and .js
},
}
tsconfig.json:
{
"transpile": false,
"compilerOptions": {
"lib": ["es2015", "dom"],
"esModuleInterop": true,
"baseUrl": ".",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true
}
}
how do I fix this?
I've also created a GH issue here: https://github.com/nuvolo/sincronia/issues/138