Header style changes depending on tsx or jsx extension

Viewed 323

I have this simple function inside app.tsx that renders a table. The header is nicely visible (white on black).

enter image description here

import React from 'react';
import { Table, Thead, Tbody, Tr, Th, Td } from "@chakra-ui/react"

export default function App() {
  return(
    <Table variant="simple" size="sm">
      <Thead>
        <Tr>
          <Th>A</Th>
          <Th>B</Th>
        </Tr>
      </Thead>
      <Tbody>
        <Tr>
          <Td>a</Td>
          <Td>b</Td>
        </Tr>
      </Tbody>
    </Table>
  )
}

However, if I change the filename instead to app.jsx, the same table will render but with a header that is hard to see:

enter image description here

To replicate this:

git clone https://github.com/somguyth/chakra-ui-header-bug.git
cd chakra-ui-header-bug
npm install
npm run dev

Then open localhost:9000 and change app.jsx to app.tsx and observe how the header color changes once the code recompiles.

The header element when it's nicely visible (app.tsx): enter image description here

The header element when it is not (app.jsx): enter image description here

EDIT: I was able to sort-of "fix" it by putting <ChakraProvider theme={darkTheme}> into app.jsx (in addition to index.tsx). Inspecting the element of the header to see why it was fixed, the class inside the th tag went back to the correct css-4v3u8n, instead of the incorrect css-r10se1. I don't know what this means or how to fix it properly (i.e., how to fix it without having to use ChakraProvider twice).


My index.tsx:

import React from "react";
import ReactDOM from "react-dom";
import { ChakraProvider, extendTheme } from "@chakra-ui/react"


import App from "./app"

const darkTheme = extendTheme({
  config: {
    useSystemColorMode: false,
    initialColorMode: "dark"
  }
});


ReactDOM.render(
    <ChakraProvider theme={darkTheme}>
        <App />
    </ChakraProvider>,
    document.getElementById("root")
);

and my module rules in webpack.config.js:

module: {
  rules: [
    {
      test: /\.tsx?$/,
      use: "ts-loader",
      exclude: /node_modules/
    },
    {
      test: /\.m?jsx?$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env', "@babel/preset-react"]
        }
      },
      resolve: {
        fullySpecified: false,
      },
    },
    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    }
  ],
},

and my .babel.rc:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

and my package.json:

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack serve"  
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@chakra-ui/icons": "^1.0.14",
    "@chakra-ui/react": "^1.6.5",
    "@emotion/react": "^11.4.0",
    "@emotion/styled": "^11.3.0",
    "css-loader": "^5.2.7",
    "framer-motion": "^4.1.17",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-table": "^7.7.0",
    "typescript": "^4.3.5"
  },
  "devDependencies": {
    "@babel/core": "^7.14.6",
    "@babel/preset-env": "^7.14.7",
    "@babel/preset-react": "^7.14.5",
    "@types/react-dom": "^17.0.9",
    "@types/react-table": "^7.7.2",
    "babel-loader": "^8.2.2",
    "html-webpack-plugin": "^5.3.2",
    "ts-loader": "^9.2.3",
    "webpack": "^5.45.1",
    "webpack-cli": "^4.7.2",
    "webpack-dev-server": "^3.11.2"
  }
}
1 Answers

It happens because there are two versions of @chakra-ui/react package in your bundle, one in cjs format and one in esm. When using React Context, two copies of the same file will create two different React Contexts (it's same as calling React.createContext in two different files), so the Consumer in one copy can't get value from Provider in another version.

This can be verified by running npx webpack build --mode production --profile --json=stats.json and loading the stats.json file into Webpack stats analyse tool which can give many internal info about your bundle:

enter image description here enter image description here

(Webpack stats analyse tool showing there are two versions of @chakra-ui/react package)


The reason of duplicated @chakra-ui/react package is that your configurations make TypeScript transpiling files to CommonJS format, and Babel transpiling files to ESModule format, while @chakra-ui/react's package.json has an exports field which specified different files for different import requests:

// @chakra-ui/react/package.json

"exports": {
  ".": {
    "require": "./dist/cjs/index.js",
    "default": "./dist/esm/index.js"
  }
}

(Webpack's documentation on package exports, basically, using require will import one file, and using import will import another file)

// Output of src/index.tsx just after ts-loader

"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importDefault(require("react"));
var react_dom_1 = __importDefault(require("react-dom"));
var react_2 = require("@chakra-ui/react");
var app_1 = __importDefault(require("./app"));
var darkTheme = react_2.extendTheme({
    config: {
        useSystemColorMode: false,
        initialColorMode: "dark"
    }
});
react_dom_1.default.render(react_1.default.createElement(react_2.ChakraProvider, { theme: darkTheme },
    react_1.default.createElement(app_1.default, null)), document.getElementById("root"));
// Output of src/app.jsx just after babel-loader

import React from 'react';
import { Table, Thead, Tbody, Tr, Th, Td } from "@chakra-ui/react";
export default function App() {
  return /*#__PURE__*/React.createElement(Table, {
    variant: "simple",
    size: "sm"
  }, /*#__PURE__*/React.createElement(Thead, null, /*#__PURE__*/React.createElement(Tr, null, /*#__PURE__*/React.createElement(Th, null, "A"), /*#__PURE__*/React.createElement(Th, null, "B"))), /*#__PURE__*/React.createElement(Tbody, null, /*#__PURE__*/React.createElement(Tr, null, /*#__PURE__*/React.createElement(Td, null, "a"), /*#__PURE__*/React.createElement(Td, null, "b"))));
}

(You can use the inspect-loader to debug loader outputs)


To resolve this issue, you can change your tsconfig.json to output ESModule:

// tsconfig.json

{
  "module": "ESNext",
  "moduleResolution": "node"
}
Related