Skip to content

Commit

Permalink
[pkg] Remove module property as it is causing issues with webpack
Browse files Browse the repository at this point in the history
  • Loading branch information
ichernev committed May 4, 2020
1 parent 4c1cc80 commit 0c709ba
Showing 1 changed file with 0 additions and 1 deletion.
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -24,7 +24,6 @@
],
"main": "./moment.js",
"jsnext:main": "./dist/moment.js",
"module": "./dist/moment.js",

This comment has been minimized.

Copy link
@karolyi

karolyi May 12, 2020

Hey,

I was trying to get a clue as to why this "module" line was the culprit but couldn't find any information on it in the official nodejs documentation.

While I welcome the fact that webpack funtionality is fixed now, can you give me some hint or a link about this package.json property?

"typings": "./moment.d.ts",
"typesVersions": {
">=3.1": {
Expand Down

4 comments on commit 0c709ba

@iamakulov
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ichernev Hey! I’m the author of moment-locales-webpack-plugin, coming here from iamakulov/moment-locales-webpack-plugin#33.

I’ve noticed Moment v2.25.2 added the module field which this commit then removed. Just curious: what problems did it cause with webpack? I’m happy to help to resolve them.

The problem with this commit is that it effectively reverts the library to v2.25.1 state. Webpack doesn’t recognize the jsnext:main field, and neither does Rollup, plus the field is generally deprecated. So publishing ES files in dist effectively does nothing: neither Node.js nor bundlers will pick it up.

@karolyi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve noticed Moment v2.25.2 added the module field which this commit then removed. Just curious: what problems did it cause with webpack? I’m happy to help to resolve them.

#5484
also, thx for the elaboration in the commit, I'll look into it tomorrow.

@iamakulov
Copy link

@iamakulov iamakulov commented on 0c709ba May 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, so the specific issue is #5484 (comment).

What exactly is happening there is that, with moment@2.25.2, when someone writes code like this:

import moment from "moment"
import "moment/locale/pl"

moment.locale("pl")

they’re effectively importing two Moment.js versions into the bundle: the CommonJS one (moment/moment.js) and the ES one (moment/dist/moment.js). Because of that, apparently, when the code calls moment.locale(), it sets the global locale in the wrong instance of Moment, and the pl locale is never applied.

Why does that code import two versions of Moment?

  • The ES version is imported because import moment from "moment" looks at moment’s package.json, notices the module field, and imports moment/dist/moment.js
  • The CommonJS version is imported because moment/locale/pl.js does require('../moment') which, in turn, requires moment/moment.js

You can confirm this by looking at the webpack-bundle-analyzer report. Here’s the report for the code from #5484 (comment) and moment@2.25.3:

image

And here’s the report for the same code and moment@2.25.2:

image

How can this be solved?

The obvious solution is to replace import "moment/locale/pl" with import "moment/dist/locale/pl". But it’s impossible: we can’t teach everyone to change their imports when they’re upgrading to a newer Moment.js.

Instead, the following workaround seems possible. In all locale files, instead of requiring the ../moment.js file, require the package root:

--- a/moment/locale/pl.js
+++ b/moment/locale/pl.js
@@ -5,6 +5,6 @@  
;(function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined'
-       && typeof require === 'function' ? factory(require('../moment')) :
+       && typeof require === 'function' ? factory(require('../')) :
-   typeof define === 'function' && define.amd ? define(['../moment'], factory) :
+   typeof define === 'function' && define.amd ? define(['../'], factory) :
   factory(global.moment)
}

This would make webpack/Node.js/whatever resolve the import based on the package.json. The loader will look at package.json, pick the right file based on "module" or "main" fields, and will import that file. So if, with moment@2.25.2, the snippet from #5484 (comment) behaves as follows:

import moment from "moment" // → looks at package.json → follows the "module" field → imports "moment/dist/moment.js"
import "moment/locale/pl" // → does `require('../moment.js')` → imports "moment/moment.js"

// → Two moment versions are bundled

– with this change, it would behave as follows:

import moment from "moment" // → looks at package.json → follows the "module" field → resolves to "moment/dist/moment.js"
import "moment/locale/pl" // → does `require('../')` → looks at package.json → follows the "module" field → resolves to "moment/dist/moment.js"

// → Only a single moment version is bundled

Nuance: If you apply this trick to moment/dist/locales/ as well, you’ll need to tune it slightly.

In moment/dist/locales/*.js, this code:

import moment from '../'

would resolve to moment/dist/index.js, not moment/package.json – and will fail. To fix that, you can maybe try
a) rewriting paths inside moment/dist/locales/ to do import moment from '../..' or
b) adding moment/dist/index.js with the code like

export * from './moment.js'

Option a) is slightly better since it makes all code follow the same resolution algorithm (so it will have fewer weird edge cases).

@AThiyagarajan
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iamakulov does the latest version fixed this issue.

I still see the error ./locale not found using version 2.27.0

Please sign in to comment.