Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webpack moment.js integration #3128

Closed
ichernev opened this issue Oct 12, 2016 · 43 comments
Closed

Webpack moment.js integration #3128

ichernev opened this issue Oct 12, 2016 · 43 comments

Comments

@ichernev
Copy link

I'm from the moment.js (http://github.com/moment) team, and some of our users that are also your users voice concerns that because of the way webpack is implemented all moment locale files (currently 104) are bundled together, and that increases the size of what is downloaded to the browser.

Recently there was a suggested "fix" in the moment code (moment/moment#3344), but then we figured it broke the require mechanism for other environments.

Also we happen to have no instructions on how to use moment and webpack (like here: http://momentjs.com/docs/#/use-it/).

Can you please give us a hand by saying what is the right way to use moment with webpack, so it won't include all locale files if the user wants so. I hope this will decrease number of issues sent to both projects :)

Note: A webpack user suggested the following: moment/moment#1435 (comment) but nobody has documented it yet: moment/momentjs.com#269

@TheLarkInn
Copy link
Member

@ichernev thank you so much for reaching out. ❤️❤️❤️❤️

We would be more than happy to help!!!

A cool idea 💡 that might be worth investigating is helping you guys create a 'example' or 'test' fixture of a small use case of webpack and moment together with one or many locales.

Pros:

  • the fixture can serve as a point of reference for users in regards to implementation
  • you can then run functional/integration smoke tests against this example for your test suite?

Thoughts?

@ichernev
Copy link
Author

ichernev commented Oct 13, 2016

@TheLarkInn hello, thank you for the prompt response.

I like the fixture idea, we can take example from require.js documentation on our website, which mentions all the different (widely used) use-cases:

http://momentjs.com/docs/#/use-it/require-js/

Basically we'll need examples for:

  • core only (no locales)
  • core with a single locale (known in advance)
  • core with all locales
  • care + dynamically loading locales (if that is applicable to webpack at all)

@rafde
Copy link
Contributor

rafde commented Oct 13, 2016

@ichernev
new webpack.IgnorePlugin(/(locale)/, /node_modules.+(momentjs)/),

@Jessidhia
Copy link
Member

Jessidhia commented Oct 13, 2016

@ichernev one problem is that your package.json points the jsnext:main entry point to your raw source code, instead of to a build of the source code that was emitted with ES6 modules. Your coding convention mostly keeps that from being a problem, but the actual issue is triggered by the relative require call.

In your main entry point, the mostly pre-bundled version, the relative require points to a locale folder that has actual locale data, so webpack happens to work there, but the require in the jsnext:main entry point points to a non-existing folder.

Now, because it is a dynamic require that webpack can parse, it actually creates a require context that can be modified by the ContextReplacementPlugin. The plugin can then be used to make it point to the correct folder, and restrict which imports are bundled.

This is how I dealt with it in my own codebase:

// restrict the extra locales that moment.js can load; en is always builtin
new webpack.ContextReplacementPlugin(/^\.\/locale$/, context => {
  // check if the context was created inside the moment package
  if (!/\/moment\//.test(context.context)) { return }
  // context needs to be modified in place
  Object.assign(context, {
    // include only japanese, korean and chinese variants
    // all tests are prefixed with './' so this must be part of the regExp
    // the default regExp includes everything; /^$/ could be used to include nothing
    regExp: /^\.\/(ja|ko|zh)/,
    // point to the locale data folder relative to moment/src/lib/locale
    request: '../../locale'
  })
})

This technically could be done with new webpack.ContextReplacementPlugin(/^\.\/locale$/, '../../locale', /^\.\/(ja|ko|zh)/), but there was no other way to make sure this would apply only to ./locale-relative requests made inside the moment module.

For dynamic loading of locales, moment would have to load locales with require.ensure instead of plain require. Webpack's parser supports conditional compilation for it with if (typeof require.ensure === 'function').

@ichernev
Copy link
Author

@Kovensky thank you for this explanation.

The dynamic require use case is purely a user thing. I mean -- moment itself doesn't have interface to say "dynamically require a locale". It only has this hack to auto require locale on npm environment with require, and I believe it is the one causing trouble with webpack.

About you example with ContextReplacementPlugin -- where should this code be placed?

About the entry point being the ES6 code vs the bundled (es5) code -- I didn't understand if your solution works with both or just the ES6 code entry point. Also can you tell webpack which entry point to use? I mean do we need to change anything on moment end to make this work?

@schmod
Copy link

schmod commented Oct 18, 2016

Here's a dumb question.

Is there any way Moment could export a separate entry point that doesn't include references to locales, so we could write import moment from 'moment/no-locales'; or require('moment/no-locales'), and subsequently/manually load the locales that we need?

This isn't going to be a great long-term solution, but it seems like it might make folks "happy enough" in the short-term, and should work seamlessly in just about any environment that understands CJS.

@TheLarkInn
Copy link
Member

@ichernev just following up to ensure all your questions are answered. When you have a PR up we would be more than happy to look it over. Or if you are at a stopping point @Kovensky could assist you further.

@TheLarkInn
Copy link
Member

TheLarkInn commented Oct 20, 2016

@ichernev also:

About you example with ContextReplacementPlugin -- where should this code be placed?

This would an item inside of the plugins property in the webpack config: which is an array of plugin instances

webpack.config.js

const webpack = require('webpack');

const config = {
  /* ... */
  plugins: [ 
  new webpack.ContextReplacementPlugin(/^\.\/locale$/, context => {
    // check if the context was created inside the moment package
    if (!/\/moment\//.test(context.context)) { return }
    // context needs to be modified in place
    Object.assign(context, {
      // include only japanese, korean and chinese variants
      // all tests are prefixed with './' so this must be part of the regExp
      // the default regExp includes everything; /^$/ could be used to include nothing
      regExp: /^\.\/(ja|ko|zh)/,
        // point to the locale data folder relative to moment/src/lib/locale
      request: '../../locale'
    })
  })

  ]
  /* ... */
}

module.exports = config;

@ichernev
Copy link
Author

@Kovensky @TheLarkInn so if I understand correctly, if the user specifies this blob in his config, the listed locales will be loaded in the "pack" and then using require a-la npm it will load them, but in case it requires something that is not "packed" it will fail?

If the user doesn't specify anything then all locales will be loadable with require (but this will increase the "pack").

I'm asking because we're discussing how to implement child locale loading: moment/moment#3336

Also is there away to go with @schmod suggestion. Basically define a separate npm/bower/git repo for webpack, that will have moment without locales and one with all locales and all locales separately and users can pack in a simpler way. I still don't understand how the packing will work in this case. Maybe the patch we reverted: https://github.com/moment/moment/pull/3344/files or the user has to somewhere specify all files he wants (so he either lists moment + a few locales, or moment-with-locales which contains all?).

@donaldpipowitch
Copy link

FYI: This problem isn't specific to moment.js. It would be nice to have a general recommendation how libs, which support multiple locales, should be used together with webpack. Other libs which I think of are https://github.com/andyearnshaw/Intl.js or https://github.com/angular/bower-angular-i18n. They don't have a require which includes all locales, but all of them need to be handled with some custom logic in some way. It would be nice to generalize the way how locale specific files are loaded.

@orditeck
Copy link

Using create-react-app, we don't have access to webpack config. And according to the main contributor there, "It's really not right that a library requires changes in the config"

Wouldn't moment/moment#2373 be more appropriate? Or maybe that's the same thing you're aiming to do?

I just don't feel like playing with webpack config is the way to go to reduce moment's size in our builds...

@kossnocorp
Copy link

kossnocorp commented Dec 5, 2016

@donaldpipowitch date-fns solves it by passing locale as an argument https://date-fns.org/docs/I18n#usage.

API looks bulky, but it's easy to write own wrappers and include all necessary locales. It's even possible to load locales on demand when the user changes the language.

// app/_lib/format.js

var format = require('date-fns/format')

var locales = {
  en: require('date-fns/locale/en'),
  eo: require('date-fns/locale/eo'),
  ru: require('date-fns/locale/ru')
}

module.exports = function (date, formatStr) {
  return format(date, formatStr, {
    locale: locales[window.__localeId__] // or global.__localeId__
  })
}

// Later:

var format = require('app/_lib/format')

window.__localeId__ = 'en'
format(friday13, 'dddd D')
//=> 'Friday 13'

window.__localeId__ = 'eo'
format(friday13, 'dddd D')
//=> 'vendredo 13'

Not sure if it's applicable to Moment.js' current state as it has a monolithic core, but still should be possible to implement in the future.

@tangoabcdelta
Copy link

Hi @ichernev & @TheLarkInn @yiminghe.

I was trying to build an app with react boilerplate using antd. Importing any component from antd results in errors.

May I recommend the aforementioned boilerplate to be the starting point for this?

@yiminghe
Copy link
Contributor

@tangoabcdelta
use

 module: {
      noParse: [/moment.js/],
}

@andrewvmail
Copy link

ERROR in ./~/antd/lib/date-picker/locale/zh_CN.js
Module not found: Error: Can't resolve 'moment/locale/zh-cn' in '/Users/andrew/Desktop/lift-app/frontend/node_modules/antd/lib/date-picker/locale'
 @ ./~/antd/lib/date-picker/locale/zh_CN.js 23:0-30
 @ ./~/antd/lib/date-picker/wrapPicker.js
 @ ./~/antd/lib/date-picker/index.js
 @ ./~/antd/lib/index.js
 @ dll reactBoilerplateDeps

Getting this problem with webpack during the dll process i have, although after that using above methods i can get webpack to watch. whats the official integration method for moment with the require locale support?

@deveedutta
Copy link

@andrewvmail @18601673727 - suggest you to read + understand + remove the lines of code that trigger this error from antd/node_modules/moment . I tried the alias approach & removed 2-3 lines of code, it has been working normally ever since.

PS: It won't work with build environments. Therefore, you may have to freeze the antd library by including in your git repo.

OTHER: ant-design/ant-design#3947

@andrewvmail
Copy link

andrewvmail commented Jan 5, 2017

@deveedutta

Thanks, yeah I was able to continue development by mucking around in the antd package in around datepicker module date-picker/locale/zh_CN.js:23

The guys at antd got me to create a new ticket at
ant-design/ant-design#4491

Nothing major just throwing it out there.

Cheers

@AaronHarris
Copy link

AaronHarris commented Feb 14, 2017

It seems this issue got sidetracked regarding the proper use of moment with webpack to reduce bundle sizes, specifically within the constraints of create-react-app. Is there a final word on this? I kind of favor the typescript style when it comes to importing locales.

@75lb
Copy link

75lb commented Feb 24, 2017

Until this is fixed, my solution is to simply avoid including moment in the webpack bundle. Load it as a regular browser script:

<script defer src="node_modules/moment/min/moment.min.js"></script>
<script defer src="node_modules/moment/locale/en-gb.js"></script>

Now you have moment in your web app at the cost of two requests and ~60Kb (as oppose to 0 additional requests but ~0.5Mb added to your bundle when using webpack).

@hakunin
Copy link

hakunin commented Mar 10, 2017

Guys this has been going on for a while and seems really easy to fix. Currently everyone who uses moment (awesome library btw) is including ALL the languages, which is insane, can we do something about it?

For now lets say current behavior doesn't change and those who want to reduce their footprint can do something like this?

import moment form 'moment/core';
import 'moment/locales/...';

@deveedutta
Copy link

Agreed.

But have to admit that people who import this are unaware of this 'language package' thing. It'd, hence, remain the responsibility of the library owner to take care of that.

@Jessidhia
Copy link
Member

One fun thing I learned yesterday -- the require call in the module version of moment is unreachable code, at least as of the webpack 2.2.0 release.

It is guarded inside an module && module.exports check, and module.exports is undefined in module code.

@michelgotta
Copy link
Contributor

@laurenskling This sounds interesting. Can you provide an example and more code with your webpack configuration.

What's the size of your bundled JavaScript file?

@laurenskling
Copy link

I've created an example package which will prove my theory: https://github.com/laurenskling/moment-treeshaking

running production mode will drop the language support.

@michelgotta
Copy link
Contributor

@laurenskling Thanks for providing the example for us.

Running your code results in a bundle.js file with ~350kB with or without commenting the import 'moment/locale/nl'; line. When checking the bundle.js file, I still find all the other languages inside. That is still a very large file for "just printing out" a date in dutch.

Can you give us more infos about your results?

@laurenskling
Copy link

hm, you're right. It still lives in the bundle. And it doesn't work. Awesome :P

@Jessidhia
Copy link
Member

Jessidhia commented Mar 31, 2017

The require call inside moment seems to have become unreachable code after webpack 2's release, as module.exports is now undefined inside ES modules, and the require is inside an if (module.exports).

@michelgotta
Copy link
Contributor

@oanogin
Copy link

oanogin commented Apr 5, 2017

Hey guys, maybe you are looking something like this one

new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(ru)$/)

and BundleAnalyzerPlugin result is

2017-04-05 11 24 54

@ichernev
Copy link
Author

ichernev commented May 1, 2017

So is this still a problem with webpack2 and should we merge some code in moment to fix this :)

@kishorevarma
Copy link

I resolved this issue in my project in a different way, I have used copy-webpack-plugin to copy all locale files to web root directory where my bundle files are exist.

following is my webpack config

 
      new CopyWebpackPlugin([
        {
          context: 'node_modules/moment/locale',
          from: '**/*',
          to: './moment-locale/'
        },
        {
          context: 'node_modules/react-intl/locale-data',
          from: '**/*',
          to: './react-intl-locale/',
          ignore: ['index.js']
        }
      ]),

In our application user can select any locale, so we need to load them dynamically. using above solution I can achieve that

Cheers
Kishore

@hoeni
Copy link

hoeni commented Jun 27, 2017

To the next person trying to get one of the above configurations to work (which they did not for me):

While trying to fix things, I accidentally stumbled upon the documentation page for the ContextReplacementPlugin showing exact this case (momentjs) as an example and it worked fine for me :-)

plugins: [
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /de|en/)
]

`

@FedC
Copy link

FedC commented Oct 11, 2017

I ran into this issue trying to use moment.js in my Angular Universal project. Since I am only interested in using it on the client for now, my workaround has been simply to inject the <script> tag with the CDN url (or from your local bower/node_modules) on the my index.html as so:

<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js" type="text/javascript"></script>

Then using it in my client-side js by declaring it first:

declare const moment: any;

It's not pretty, but it meets my needs for now.

@shavidzet
Copy link

shavidzet commented Feb 18, 2018

To use for global scope (access with window.moment) with webpack loader

Install
yarn add moment expose-loader

Add a loader:

      {
        test: require.resolve('moment'),
        use: [
          {
            loader: 'expose-loader',
            options: 'moment'
          }
        ]
      },

import from entry files

import 'moment'

@mryarbles
Copy link

How the !@#$!@#$ is this still a problem?

@ronbnsim
Copy link

it just doesnt work!!!

@TheLarkInn
Copy link
Member

Since this doesn't really have anything to do with webpack at this point I think it's best to close this issue. If anyone would like to create a guide on webpack.js.org they can submit a PR to GitHub.com/webpack/webpack.js.org

@marcosfede
Copy link

Hi, I'm using some libraries and each one is bundling its own version of moment. How would I avoid duplication with webpack so that I bundle only one version of moment?

@ghost
Copy link

ghost commented Feb 18, 2019

Can't personally vouch for it but have read about Day.Js as an alternative

⏰ Day.js 2KB immutable date library alternative to Moment.js with the same modern API

https://github.com/iamkun/dayjs

reference: moment/moment#2373 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests