material-color-utilities icon indicating copy to clipboard operation
material-color-utilities copied to clipboard

.js in import of ESM

Open WordlessEcho opened this issue 3 years ago • 10 comments

I am using Yarn to download this package:

yarn add @material/material-color-utilities

After fixing #29 I got these error:

$ C:\files\src\hct\node_modules\.bin\ts-node-esm .\index.ts
C:\files\src\hct\node_modules\ts-node\dist-raw\node-esm-resolve-implementation.js:383
    throw new ERR_MODULE_NOT_FOUND(
          ^
CustomError: Cannot find module 'C:\files\src\hct\node_modules\@material\material-color-utilities\dist\blend\blend' imported from C:\files\src\hct\node_modules\@material\material-color-utilities\dist\index.js
    at finalizeResolution (C:\files\src\hct\node_modules\ts-node\dist-raw\node-esm-resolve-implementation.js:383:11)
    at moduleResolve (C:\files\src\hct\node_modules\ts-node\dist-raw\node-esm-resolve-implementation.js:818:10)
    at Object.defaultResolve (C:\files\src\hct\node_modules\ts-node\dist-raw\node-esm-resolve-implementation.js:929:11)
    at C:\files\src\hct\node_modules\ts-node\src\esm.ts:228:33
    at entrypointFallback (C:\files\src\hct\node_modules\ts-node\src\esm.ts:179:34)
    at resolve (C:\files\src\hct\node_modules\ts-node\src\esm.ts:227:12)
    at resolve (C:\files\src\hct\node_modules\ts-node\src\child\child-loader.ts:19:39)
    at ESMLoader.resolve (node:internal/modules/esm/loader:580:30)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:294:18)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:80:40)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

When I checking dist\index.js:

export * from './blend/blend';

I made some changes to this:

export * from './blend/blend.js';

After I adding .js to all imports. It works perfect to me.

More info: export - JavaScript | MDN

WordlessEcho avatar Mar 29 '22 10:03 WordlessEcho

same bug typescript is not support esm but build package with esm..... we can use

export async function loadEsmModule<T>(modulePath: string|URL): Promise<T> {
  const namespaceObject =
      (await new Function('modulePath', `return import(modulePath);`)(modulePath));

  // If it is not ESM then the values needed will be stored in the `default` property.
  // TODO_ESM: This can be removed once `@angular/*` packages are ESM only.
  if (namespaceObject.default) {
    return namespaceObject.default;
  } else {
    return namespaceObject;
  }
}

like this but is not perfect

wszgrcy avatar Apr 25 '22 09:04 wszgrcy

FWIW, Node.js requires file extensions and it's likely required in a browser environment, too. Skipping them is really only a feature of various packagers. From "import specifiers – Mandatory file extensions" in Node.js API docs for ECMAScript modules:

A file extension must be provided when using the import keyword to resolve relative or absolute specifiers. Directory indexes (e.g. './startup/index.js') must also be fully specified.

This behavior matches how import behaves in browser environments, assuming a typically configured server.

Some more information on this can be read in the TypeScript 4.7 Beta announcement:

This code works in CommonJS modules, but will fail in ES modules because relative import paths need to use extensions. As a result, it will have to be rewritten to use the extension of the output of foo.ts – so bar.ts will instead have to import from ./foo.js.

Basically, the suggested fix is to add .js to all imports, even in TypeScript code:

This might feel a bit cumbersome at first, but TypeScript tooling like auto-imports and path completion will typically just do this for you.

That would fix it for Node.js. I'm not sure if it would break stuff in e.g. Webpack.

For now, I'm using a loader when using this package with Node.js. Use it like this: node --experimental-loader=./module-loader.mjs <script>

/**
 * This module contains loader hooks to force loading `@material/material-color-utilities` as ECMAScript modules, even though its imports lack file endings.
 */

import { createRequire } from 'module';

const PACKAGE_NAME = '@material/material-color-utilities';

/**
 * `resolve` loader hook to force loading `@material/material-color-utilities` as ECMAScript modules.
 *
 * The package's imports lack file endings (e.g. ".js"), so we need to resolve them ourselves. This requires a loader hook.
 *
 * @param specifier {string}
 * @param context {{ conditions: string[], parentURL?: string }}
 * @param defaultResolve {any}
 * @returns {{ format: string, url: string }}
 */
export async function resolve(specifier, context, defaultResolve) {
  const specifierInsidePackage =
    context.parentURL && context.parentURL.includes(PACKAGE_NAME);

  if (specifierInsidePackage) {
    const require = createRequire(context.parentURL);
    const url = new URL(require.resolve(specifier), 'file://').toString();

    return {
      format: 'module',
      url,
    };
  }

  return defaultResolve(specifier, context, defaultResolve);
}

victorandree avatar Apr 25 '22 10:04 victorandree

AFAICT, TypeScript emits imports in the same style as they appear in the source file. That means to fix this issue, you just need to append .js to all import and export declarations in typescript/**/*.ts files. E.g. file index.ts should look like this:

export * from './blend/blend.js';
export * from './hct/cam16.js';
export * from './hct/hct.js';
...

pschiffmann avatar Apr 27 '22 07:04 pschiffmann

When you get this typescript error:

./node_modules/@material/material-color-utilities/dist/index.d.ts:17:15 - error TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Did you mean './blend/blend.js'?

17 export * from './blend/blend';

Then as a workaround, pin the package to version 0.1.1 in your package.json, like this:

{
  "dependencies": {
    "@material/material-color-utilities": "0.1.1"
  }
}

This workaround will be unnecessary when the fix from my previous comment is implemented and published on npm.

pschiffmann avatar Apr 27 '22 07:04 pschiffmann

If webpack is used, configuring fullyspecificed can solve

{
  test: /\.m?js$/,
  resolve: {
    fullySpecified: false,
  },
},

hungtcs avatar Apr 28 '22 09:04 hungtcs

Hello, I wish to know if a pull request for this issue would be accepted anyway, given its chore-esque nature

immjs avatar May 22 '22 12:05 immjs

@immjs Hi. I don't know about that. You can check pr history for details.

WordlessEcho avatar May 28 '22 14:05 WordlessEcho

This command will append .js to all import and export declarations in all js files

find './node_modules/@material/material-color-utilities' -type f -name '*.js' -exec \
sed -Ei '/(import|export)/s/(\x22|\x27)(\.{1,2}\/(([[:alnum:]_-]+|\.{2})\/)*[[:alnum:]_-]+)(\.js)?\1/\1\2.js\1/g' {} +

Execute in project directory!

if you want it to be done automatically after npm upgrade or npm rebuild, add postinstall script to your project package.json

{
  "type": "module",
  "dependencies": {
    "@material/material-color-utilities": "^0.1.2"
  },
  "scripts": {
    "postinstall": "find './node_modules/@material/material-color-utilities/' -type f -name '*.js' -exec sed -Ei '\/(import|export)\/s\/(\\x22|\\x27)(\\.{1,2}\\\/(([[:alnum:]_-]+|\\.{2})\\\/)*[[:alnum:]_-]+)(\\.js)?\\1\/\\1\\2.js\\1\/g' {} +"
  }
}

EDIT: Improved "parent" path validation! https://regex101.com/r/OUTBH4/2

observ33r avatar Jun 26 '22 07:06 observ33r

I am facing the same issue. I am using rollup for bundling. Can someone guide, how can I resolve this?

Did you mean 'blend.js'?
BREAKING CHANGE: The request './blend/blend' failed to resolve only because it was resolved as fully specified
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.```

omkar-nath avatar Aug 08 '22 15:08 omkar-nath

Hi all, since @material/material-color-utilties not accepting external contributions, I created a fork @importantimport/material-color-utilties.

It replaces tsc with unbuild to fix this issue and support CJS, and it works fine for now. (only partially tested)

feel free to use it!

kwaa avatar Aug 13 '22 10:08 kwaa

Is there any word from the Material Foundation team regarding this? Seems like it would be at least on their radar, considering a design choice has resulted in the package being unusable in most development environments (see: everyone's using a framework).

WillsterJohnson avatar Oct 05 '22 20:10 WillsterJohnson

Thanks for your patience. This will be fixed in the next release, closing

guidezpl avatar Feb 17 '23 10:02 guidezpl