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.
Related
We are using typescript and exporting one class as following (we are using nuxt and class style component if this relates to webpack issue).
export class ApiService {
static apiHost = '/';
}
When we try to import it as following, it gives ApiService as undefined and so gives error on apiHost access as ApiService gets undefined.
import { ApiService } from '../services/api-service';
my tsconfig is as following.
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"removeComments": true,
"noLib": false,
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowJs": true,
"target": "esnext",
"resolveJsonModule": true,
"sourceMap": true,
"outDir": "./dist",
"esModuleInterop": true,
"moduleResolution": "node",
"baseUrl": "./",
"skipLibCheck": true, // TODO: Remove. Temporary. https://github.com/nuxt/typescript/issues/49
"paths": {
"~/*": ["server/*"],
"#/*": [
"client/*"
],
},
"types": [
"node",
"#types/node",
"#nuxt/vue-app",
"vuex-class-component/dist",
]
},
"include": [
"server/**/*",
"client/**/*"
],
"exclude": [
"node_modules"
]
}
my webpack.config.js
const webpack = require('webpack');
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const WebpackShellPlugin = require('webpack-shell-plugin');
module.exports = {
entry: ['webpack/hot/poll?1000', './server/main.ts'],
watch: true,
target: 'node',
externals: [
nodeExternals({
allowlist: ['webpack/hot/poll?1000'],
}),
],
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
mode: 'development',
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new WebpackShellPlugin({ onBuildEnd: ['node dist/main.js'] }),
],
output: {
path: `${__dirname}/dist`,
publicPath: '/',
},
};
Command I am running
ts-node -r tsconfig-paths/register server/main.ts
Actually I didn't post entire code as I thought it wasn't necessary.
But service was also calling back the class where service was imported.
import { AuthConfig } from '../config/auth-config';
export class ApiService {
static apiHost = '/';
static mgr = new Oidc.UserManager(
new AuthConfig().IdentityServerOAuth2Config, // This was causing issue as it was causing cyclic imports.
);
}
Follwing is the code where cyclic dependency was happening.
import ApiService from '../services/api-service';
export class AuthConfig {
`${ApiService.apiHost}api/callback` // This was again calling back the apiService resulting in cyclic dependency.
}
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?
I have a typescript react project, with the following tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"downlevelIteration": true,
"experimentalDecorators": true,
"declaration": false,
"noUnusedLocals": false,
"removeComments": true,
"jsx": "react",
"types": [
"react"
],
"noImplicitAny": true,
"outDir": "./dist/",
"preserveConstEnums": true,
"sourceMap": true,
"typeRoots": [
"./types",
"node_modules/#types"
],
"strictNullChecks": true,
"baseUrl": "./",
},
"include": [
"src"
],
"exclude": [
"dist",
"node_modules"
]
}
when I do a dev run:
"webpack-dev-server --host 0.0.0.0 --port 3000 --hot --progress --colors --history-api-fallback --config webpack.dev.config.js"
It works fine, but if I do a prod build with the following webpack.prod.config.js file:
const webpack = require('webpack');
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const devFlagPlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')),
});
module.exports = {
mode: 'production',
entry: {
app: './src/index.tsx',
vendor: ['react', 'react-dom', 'react-router-dom', 'redux', 'react-redux', 'redux-thunk'],
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
},
target: 'web',
devtool: 'source-map',
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
loader: 'awesome-typescript-loader',
},
{
enforce: 'pre',
test: /\.js$/,
loader: 'source-map-loader',
},
{
test: /.svg$/,
use: ['#svgr/webpack'],
},
{
test: /\.css$/,
include: /app|node_modules/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /.(jpg|png|gif|eot|woff|ttf|svg)/,
loader: 'file-loader',
},
],
},
plugins: [
new MiniCssExtractPlugin('[name].css'),
devFlagPlugin,
new webpack.ProvidePlugin({
React: 'react',
}),
],
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 6,
},
}),
new OptimizeCSSAssetsPlugin({}),
],
},
};
It is also built successfully, but when I server the content and then go to localhost:80, the page failed to open, with following error in the console log:
TypeError: Object(...) is not a function
M index.es.js:382
React 11
<anonymous> index.tsx:36
Webpack 3
This problem cannot be reproduce with the dev build, so it seems the ts codes were okay, but the prod build didn't perform as expected. What did I miss in the webpack build setup?
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()],
I am creating a build process for a library we have published on NPM. We are migrating from webpack to rollup for treeshaking and ESM support.
The issue we are facing is that when we analyze the final bundle, I can see exactly what we are importing from the node_modules I cannot see the same for the main index.js and our own components/files
If I only compile the library using typescript, this the following output:
tsconfig.json
{
"include": ["src"],
"exclude": [
"lib",
"**/*.stories.tsx",
"**/*.stories.ts",
"**/*.test.ts",
"**/*.test.tsx",
"**/__mocks__/*",
"node_modules"
],
"compilerOptions": {
"noImplicitAny": false,
"noImplicitReturns": false,
"noImplicitThis": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"target": "es5",
"module": "ESNext",
"moduleResolution": "node",
"lib": ["es6", "dom", "es2016", "es2017"],
"sourceMap": true,
"declaration": true,
"allowSyntheticDefaultImports": true,
"outDir": "lib",
"jsx": "react",
"allowJs": false,
"suppressImplicitAnyIndexErrors": true,
"esModuleInterop": true
}
}
If I build using Rollup, this is the output:
rollup.config.js
import pkg from './package.json';
import typescript from 'rollup-plugin-typescript2';
import commonjs from '#rollup/plugin-commonjs';
import resolve from '#rollup/plugin-node-resolve';
import external from 'rollup-plugin-peer-deps-external';
import { terser } from 'rollup-plugin-terser';
/**
* Config tweaked from: https://www.pluralsight.com/guides/react-typescript-module-create
*/
export default {
input: 'src/index.ts',
output: [
{ file: pkg.main, format: 'cjs', sourcemap: true, exports: 'named' },
{ file: pkg.module, format: 'es', sourcemap: true, exports: 'named' },
],
plugins: [
external(), // prevents from bundling peer dependencies
resolve(), // bundles third party dependencies we've installed in node_modules.
typescript({
rollupCommonJSResolveHack: true,
clean: true,
}),
commonjs({
// compiles js files into commonjs
include: /node_modules/,
namedExports: {
'node_modules/react/react.js': ['Children', 'Component', 'PropTypes', 'createElement'],
'node_modules/react-dom/index.js': ['render'],
'node_modules/react-dates/index.js': [
'DayPickerRangeController',
'CalendarInfoPositionShape',
'DateRangePicker',
'SingleDatePicker',
],
'node_modules/xss/lib/index.js': ['DEFAULT', 'whiteList'],
'node_modules/uuid/index.js': ['v4'],
},
}),
terser(), // uglify and minification of bundle
],
};
I want to be able to achieve the first result (the one using the TSC compiler) where I can keep the library project structure, but using rollup building process to take advantage of treeshaking and minification process.
What am I missing from my configuration?
you can trying by replacing your plugin's filed by the code as below:
nodeResolve({
browser: true,
dedup: ['react', 'react-dom'],
extensions,
}),
postcss({
extract: join(outputDir, 'index.css'),
modules: {
generateScopedName: cssScopeName,
},
namedExports: cssExportName,
plugins: [
postcssImport(),
],
}),
typescript({
allowNonTsExtensions: true,
useTsconfigDeclarationDir: true,
objectHashIgnoreUnknownHack: true,
tsconfigDefaults: {
compilerOptions: {
baseUrl: sourceDir,
target: 'esnext',
jsx: 'preserve',
emitDecoratorMetadata: true,
allowSyntheticDefaultImports: true,
experimentalDecorators: true,
paths: {
[[pkg.name, "*"].join("/")]: [join(outputDir, pkg.name, "*")]
}
},
}
}),
commonjs({
include: [resolve(rootPath, 'node_modules/**')],
exclude: [resolve(rootPath, 'node_modules/process-es6/**')],
namedExports:{
[resolve(rootPath, 'node_modules/react/index.js')]: [
"Children",
"Component",
"PropTypes",
"createRef",
"useEffect",
"useState",
"useContext",
"useRef", ],
[resolve(rootPath, "node_modules/react-dom/index.js")]: ["render", "findDOMNode"],
[resolve(rootPath, "node_modules/react-is/index.js")]: ["isValidElementType"]
},
}),
babel({
babelrc: false,
extensions,
presets: [
[ '#babel/preset-env', { modules: false } ],
[ '#babel/preset-react' ],
],
}),
......