palantir / blueprint

A React-based UI toolkit for the web
https://blueprintjs.com/
Apache License 2.0
20.74k stars 2.18k forks source link

DateTime2 components break ts-node execution (testing with mocha) #5536

Closed JoseLion closed 2 years ago

JoseLion commented 2 years ago

Environment

Code Sandbox

N/A

Steps to reproduce

  1. Create a component using one or more of the @blueprintjs/datetime2 package component
  2. Make a simple .ts test for the component
  3. Set up ts-node and mocha to run the tests without the need to compile the .ts file first
  4. Run the test script. An error should be thrown.

Actual behavior

The following error is thrown before the tests can start running:

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".tsx" for path/to/your/file.test.tsx
    at new NodeError (node:internal/errors:393:5)
    at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:80:11)
    at defaultGetFormat (node:internal/modules/esm/get_format:122:38)
    at defaultLoad (node:internal/modules/esm/load:81:20)
    at nextLoad (node:internal/modules/esm/loader:163:28)
    at ESMLoader.load (node:internal/modules/esm/loader:602:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:458:22)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:477:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:435:34)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:527:24)
    at async importModuleDynamicallyWrapper (node:internal/vm/module:438:15)
    at async formattedImport (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\nodejs\esm-utils.js:7:14)
    at async Object.exports.requireOrImport (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\nodejs\esm-utils.js:38:28)
    at async Object.exports.loadFilesAsync (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\nodejs\esm-utils.js:91:20)
    at async singleRun (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\cli\run-helpers.js:125:3)
    at async Object.exports.handler (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\cli\run.js:370:5)

Expected behavior

Tests should run with no problem with ts-node

Possible solution

Based on the stack trace of the error, it looks like the problem is related to the ES module loader. I couldn't find any different configuration in the @blueprintjs/datetime2 package compared to @blueprintjs/datetime except for one extra dependency in datetime2: "lodash-es": "^4.17.15"

On that same note, if I try to set up mocha to use the experimental ts-node/esm loader I get the following error:

(node:15784) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

Error [ERR_REQUIRE_ESM]: require() of ES Module C:\Repos\orkestra\orkestra-web\node_modules\lodash-es\lodash.js from C:\Repos\orkestra\orkestra-web\node_modules\@blueprintjs\datetime2\lib\cjs\common\getTimezone.js not supported.
Instead change the require of lodash.js in C:\Repos\orkestra\orkestra-web\node_modules\@blueprintjs\datetime2\lib\cjs\common\getTimezone.js to a dynamic import() which is available in all CommonJS modules.
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at require.extensions.<computed> [as .js] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\node_modules\@blueprintjs\datetime2\lib\cjs\common\getTimezone.js:19:19)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at require.extensions.<computed> [as .js] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\node_modules\@blueprintjs\datetime2\lib\cjs\components\date-input2\dateInput2.js:27:21)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at require.extensions.<computed> [as .js] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\node_modules\@blueprintjs\datetime2\lib\cjs\components\index.js:19:20)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at require.extensions.<computed> [as .js] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\node_modules\@blueprintjs\datetime2\lib\cjs\index.js:20:22)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at require.extensions.<computed> [as .js] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:851:20)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\src\components\commons\fields\date-field\DateField.component.tsx:5:21)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at require.extensions.<computed> [as .tsx] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\src\components\commons\fields\Fields.ts:8:31)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at require.extensions.<computed> [as .ts] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\src\components\commons\input-dialog\InputDialog.component.tsx:10:18)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at require.extensions.<computed> [as .tsx] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\src\store\main.provider.tsx:10:33)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at require.extensions.<computed> [as .tsx] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\src\App.tsx:9:25)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at require.extensions.<computed> [as .tsx] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at Object.<anonymous> (C:\Repos\orkestra\orkestra-web\test\integration\App.test.tsx:6:15)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at m._compile (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:857:29)
    at require.extensions.<computed> (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at require.extensions.<computed> [as .tsx] (C:\Repos\orkestra\orkestra-web\node_modules\ts-node\dist\index.js:859:16)
    at async Promise.all (index 0)
    at async formattedImport (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\nodejs\esm-utils.js:7:14)
    at async exports.requireOrImport (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\nodejs\esm-utils.js:38:28)
    at async exports.loadFilesAsync (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\nodejs\esm-utils.js:91:20)
    at async singleRun (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\cli\run-helpers.js:125:3)
    at async exports.handler (C:\Repos\orkestra\orkestra-web\node_modules\mocha\lib\cli\run.js:370:5)
wmira commented 2 years ago

I'm getting this error with nextjs as well preventing me from using datetime2

adidahiya commented 2 years ago

There's nothing fundamentally different in the datetime2 package as far as TS configuration and the emitted CommonJS / ES module .js files (compared to core, datetime, etc.).

I suspect the problem has to do with configuration of ts-node, mocha, or something else in your build stack.

wmira commented 2 years ago

There's nothing fundamentally different in the datetime2 package as far as TS configuration and the emitted CommonJS / ES module .js files (compared to core, datetime, etc.).

I suspect the problem has to do with configuration of ts-node, mocha, or something else in your build stack.

issue is lodash-es is published as an esm. It is used by the cjs output by datetime2 and this causes error as some bundler does not transform anything in node_modules

https://github.com/palantir/blueprint/blob/develop/packages/datetime2/src/common/getTimezone.ts#L17

and inside lib/cjs/common/getTimezone.js

exports.getCurrentTimezone = void 0; var lodash_es_1 = require("lodash-es");

requiring lodash-es , which inside lodash-es uses import (esm) Error: require() of ES Module /home/wmira/dev/x123/node_modules/lodash-es/lodash.js from /home/wmira/dev/x123/node_modules/@blueprintjs/datetime2/lib/cjs/common/getTimezone.js not supported. Instead change the require of lodash.js in /home/wmira/dev/x123/node_modules/@blueprintjs/datetime2/lib/cjs/common/getTimezone.js to a dynamic import() which is available in all CommonJS modules.

wmira commented 2 years ago

@adidahiya if you search:

https://github.com/palantir/blueprint/search?q=lodash-es

DateTime2 is the one that is using lodash-es? maybe it should use the normal cjs lodash package instead?

wmira commented 2 years ago

possible we change the import to:

import memoize from 'lodash/memoize'

https://github.com/palantir/blueprint/blob/7c639e95e4079bdd4fbe56a6fc2de1a24dbdafd8/packages/datetime2/src/common/getTimezone.ts#L17

and

import isEmpty from 'lodash/isEmpty'

https://github.com/palantir/blueprint/blob/81147fc2b5edb3a1ed36fe959c03b568c7461b68/packages/datetime2/src/common/timezoneUtils.ts#L18

wmira commented 2 years ago

for those having issue under nextjs, try this fix:

/** we import the esm version of the lib */
import { DateInput2 } from "@blueprintjs/datetime2/lib/esm"; 

use next-transpile-modules on next.config.js to ignore datetime2 and lodash-es

/** next.config.js */
const withTM = require('next-transpile-modules')(['@blueprintjs/datetime2', 'lodash-es']);

const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
}

module.exports = withTM(nextConfig)

and above should work with DateTime2

adidahiya commented 2 years ago

Ah, ok, I see the problem now. I guess we have a couple options here:

  1. stop supporting cjs modules in @blueprintjs/datetime2, only export the /lib/esm folder and omit /lib/cjs
  2. switch to submodule imports of lodash like you suggested, e.g. import isEmpty from "lodash/isEmpty"

(1) is something I'd like to do eventually, but I don't think that's a breaking change I want to commit to for Blueprint v5, so I think we're stuck with option (2) for now. Maybe in Blueprint v6 we will drop CommonJS modules and only export ES modules as the package public APIs.

adidahiya commented 2 years ago

Doing some research for this, it looks like many other library authors hit this problem with optimizing lodash imports as well. There are a few solutions out there to simultaneously support CJS and ESM, but they don't look very attractive to me at the moment. I think the cleanest approach is to drop CJS support in a future major version as I suggested above.

See: