tools: [build] polymer build does not handle .mjs files

Unlike polymer serve, polymer build doesn’t know to treat .mjs files as JavaScript files.

import {
  createStore,
  compose,
  applyMiddleware,
  combineReducers
} from 'redux/es/redux.mjs';
       ~~~~~~~~~~~~~~~~~~~~

file:///Users/keanulee/Code/Polymer/pwa-starter-kit/src/store.js(16,8) error [could-not-load] - Unable to load import: No parser for for file type mjs

Another example: https://github.com/Polymer/pwa-starter-kit/pull/249

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Comments: 17 (13 by maintainers)

Most upvoted comments

This would be more applicable to 3p libraries, for example https://unpkg.com/redux@4.0.1/es/redux.mjs (https://github.com/reduxjs/redux/pull/3143 from @TimvdLippe ).

Is there a way so Polymer prioritizes .mjs files?

I have a package with a bundle in a subfolder

swiss-element/hooks.mjs => ESM
swiss-element/hooks.js => UMD

And Polymer is picking up the .js first with import { useEffect } from 'swiss-element/hooks';

which results in an error because CJS modules are not supported.

This works however import { useEffect } from 'swiss-element/hooks.mjs';

I know it’s a small detail but my project uses suffix less syntax for npm packages…

Why not just use .js files?

See also https://github.com/Polymer/tools/issues/671

Patch: #792. Regardless of any one project’s decision to use *.mjs or *.js for its source files, tooling must support *.mjs simply because people are using it.

I think this immediate issue can be closed now that #792 is merged.

Is there any agreement around .mjs extension set in stone anywhere so that we could refer to? The statement like “because people are using it” does not provide enough information about whether people are doing right and isn’t it too early to use that extension at this point.

Especially, webpack maintainer expressed the position regarding .mjs here: https://github.com/webpack/webpack/issues/7482#issuecomment-394838925

Note that .mjs and ESM in .js behaves different. .mjs tries to be as compatible as possible to node.js. This means no module field and no __esModule for .mjs files. Also note that .mjs is still experimental until node.js support has finalized.

IMO, #792 seems reasonable given the current “file extension implies media type” behavior of the analyzer. It might be nice to split this process into two steps at some point to help out with forward compatibility:

Rather than just

parsers = new Map<string, Parser<ParsedDocument>>([
  ['html', new HtmlParser()],
  ['js', new JavaScriptParser()],
  ['mjs', new JavaScriptParser()],
  ['css', new CssParser()],
  ['json', new JsonParser()],
]);

maybe doing something more like

defaultMediaType = new Map<string, string>([
  ['html', 'text/html'],
  ['css', 'text/css'],
  ['js', 'application/javascript'],
  ['mjs', 'application/javascript'],
  ['json', 'application/json'],
  ...userSuppliedDefaultMediaTypes
]);

parsers = new Map<string, Parser<ParsedDocument>>([
  ['text/html', new HtmlParser()],
  ['text/css', new CssParser()],
  ['application/javascript', new JavaScriptParser()],
  ['application/json', new JsonParser()],
]);

and allowing the user to supply a ‘file to media type’ map + defaults for extensions somewhere like this

{
  // This section is used above as `userSuppliedDefaultMediaTypes`:
  "extensionToDefaultMediaType": [
    ["es", "application/javascript"],
  ],

  "fileToMediaType": [
    ["./path/to/a-file-with-a.strange-extension", "text/html"],
  ],
}

so that the process for taking a file and finding an appropriate parser becomes something like

function getParserForFileAtPath(path) {
  if (fileToMediaType.has(path)) {
    return parser.get(fileToMediaType.get(path));
  }

  if (defaultMediaType.has(extensionForPath(path))) {
    return parser.get(defaultMediaType.has(extensionForPath(path));
  }

  throw new Error("Can't figure out how to load your thing.");
}