How to prevent moment.js from loading locales with webpack? - javascript

Is there any way you can stop moment.js from loading all the locales (I just need English) when you're using webpack? I'm looking at the source and it seems that if hasModule is defined, which it is for webpack, then it always tries to require() every locale. I'm pretty sure this needs a pull request to fix. But is there any way we can fix this with the webpack config?
Here is my webpack config to load momentjs:
resolve: {
alias: {
moment: path.join(__dirname, "src/lib/bower/moment/moment.js")
},
},
Then anywhere I need it, I just do require('moment'). This works but it's adding about 250 kB of unneeded language files to my bundle. Also I'm using the bower version of momentjs and gulp.
Also if this can't be fixed by the webpack config here is a link to the function where it loads the locales. I tried adding && module.exports.loadLocales to the if statement but I guess webpack doesn't actually work in a way where that would work. It just requires no matter what. I think it uses a regex now so I don't really know how you would even go about fixing it.

The code require('./locale/' + name) can use every file in the locale dir. So webpack includes every file as module in your bundle. It cannot know which language you are using.
There are two plugins that are useful to give webpack more information about which module should be included in your bundle: ContextReplacementPlugin and IgnorePlugin.
require('./locale/' + name) is called a context (a require which contains an expression). webpack infers some information from this code fragment: A directory and a regular expression. Here: directory = ".../moment/locale" regular expression = /^.*$/. So by default every file in the locale directory is included.
The ContextReplacementPlugin allows to override the inferred information i.e. provide a new regular expression (to choose the languages you want to include).
Another approach is to ignore the require with the IgnorePlugin.
Here is an example:
var webpack = require("webpack");
module.exports = {
// ...
plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /de|fr|hu/)
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
};

In our project, I include moment like this: import moment from 'moment/src/moment'; and that seems to do the trick. Our usage of moment is very simple though, so I'm not sure if there will be any inconsistencies with the SDK. I think this works because WebPack doesn't know how to find the locale files statically, so you get a warning (that's easy to hide by adding an empty folder at moment/src/lib/locale/locale) but no locale includes.

UPDATE: 2021
There are many other libs that you may want to checkout:
https://date-fns.org
https://github.com/iamkun/dayjs
ORIGINAL ANSWER:
Seems like the proper modular moment library will never come up However, I just ended up of using https://github.com/ksloan/moment-mini like import * as moment from 'moment-mini';

As of moment 2.18, all locales are bundled together with the core library (see this GitHub issue).
The resourceRegExp parameter passed to IgnorePlugin is not tested against the resolved file names or absolute module names being imported or required, but rather against the string passed to require or import within the source code where the import is taking place. For example, if you're trying to exclude node_modules/moment/locale/*.js, this won't work:
new webpack.IgnorePlugin({ resourceRegExp: /moment\/locale\// });
Rather, because moment imports with this code:
require('./locale/' + name);
your first regexp must match that './locale/' string. The second contextRegExp parameter is then used to select specific directories from where the import took place. The following will cause those locale files to be ignored:
plugins:[
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment$/,
}),
]
which means "any require statement matching './locale' from any directories ending with 'moment' will be ignored.

Based on Adam McCrmick's answer, you were close, change your alias to:
resolve: {
alias: {
moment: 'moment/src/moment'
},
},

With webpack2 and recent versions of moment you can do:
import {fn as moment} from 'moment'
And then in webpack.config.js you do:
resolve: {
packageMains: ['jsnext:main', 'main']
}

Here's another solution using postinstall script in NPM installer.
You can put a line to your package.json file:
{
"scripts": {
...
"postinstall": "find node_modules/moment/locale -type f -not -name 'en-gb.js' -not -name 'pl.js' -printf '%p\\n' | xargs rm"
...
}
}
In result unwanted locales will be removed immediately after npm install finish installing packages.
In my case only en-gb and pl locales will remain in bundle.
In case you already have postinstall script, you can add script to existing commands:
{
"scripts": {
...
"postinstall": "previous_command && find node_modules/moment/locale -type f -not -name 'en-gb.js' -not -name 'pl.js' -printf '%p\\n' | xargs rm"
...
}
}

Related

How to use TypeScript w/ a library with no definition file?

So I’m trying to use the pino-clf library in my koa TS project.
I keep getting this when I try to compile:
TSError: ⨯ Unable to compile TypeScript:
src/modules/logger/index.ts:5:21 - error TS7016: Could not find a declaration file for module 'pino-clf'. '/dev/webservices/node_modules/pino-clf/index.js' implicitly has an 'any' type.
Try `npm i --save-dev #types/pino-clf` if it exists or add a new declaration (.d.ts) file containing `declare module 'pino-clf';`
5 import pinoClf from 'pino-clf'
~~~~~~~~~~
pino-clf doesn’t have a def file and there’s no #types/pino-clf available.
I tried adding a pino-clf.d.ts file in the folder of the file that I’m importing the lib into w/ declare module 'pino-clf' in it. While that got the red squigglies in my IDE to go away, TS still refuses to compile.
How in the world do we use use a lib that’s just plain JS w/ TS and w/o adding a ts-ignore?
So there are two ways this can be accomplished.
Solution One: Probably the easiest
You can just use require ex: const pinoClf = require("pinoClf") - the down-side is that dot reference or intellisense isn't available but if you know the methods you want to use its no biggie.
Solution Two:
Create you own typeDef file in the root of you project. For example,
pino-clf.custom.d.ts
declare module "pino-clf.custom" {
const pinoClfJs = require("pinoClf");
export default class pinoClf {
commonLog (type: string, dest: NodeJS.WriteStream, ancillary: any): void {
pinoClfJs.commonLog(type, dest, ancillary);
}
}
}
then in you tsconfig.json file include the new typeDef file:
{
... // assuming src is already there
"include": [
"src", "pino-clf.custom.d.ts"
]
}
after that you can simply import it import pinoClf from "pino-clf.custom";
This is a very basic implementation, and recommend researching if you desire something more complex. Of course there is more than one way to solve a problem but, I hope this helped. Cheers.

import css file into es6 returns string instead of object

TL;DR
I'm importing a css file into a typescript module, but the import resolves to a string instead of an object. Can anyone tell me why I don't get an object??
Example
// preview.ts
import test from './src/assets/test.theme.css';
// also tried this:
// import * as test from './src/assets/test.theme.css';
console.log('typeof test: ', typeof test);
console.log(test);
Console output
Detailed explanation
Actually, I'm trying to set up a Storybook for my Angular12 component library.
In order to provide various themes, I want to use the #etchteam/storybook-addon-css-variables-theme plugin, which in its documentation refers to the inline loader syntax of Webpack.
import myTheme from '!!style-loader?injectType=lazyStyleTag!css-loader!./assets/my-theme.css';
When applying this to my code my browser console started to complain
Error: myTheme.use is not a function
During my research I recognized that the imported stylesheet is not an evaluated javascript object, but instead it is provided as a string containing the sourcecode generated by the style-loader.
I also recognized, that this issue is not specific to the style-loader, but also occurs for all other loaders, e.g. css-loader, raw-loader, etc.
This issue is also not related to inline loader syntax, as it also shows up with loaders being defined in a minimalistic webpack config.
Environment:
Angular 12
Webpack 5
Reproduction
I have set up a GIT repo reproducing the issue.
The readme file explains the repro and the issue.
I think you have mistake in your Webpack config. You have nested rules property, instead you should have use:
{
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
}
https://webpack.js.org/loaders/css-loader/
I'm sorry, but I have to revert my last statement. My issue has NOT been resolved by #Akxe's comment.
Now my import statement (import * as test from '...') resolves to an object, but it's still not correct.
I have set up a GIT Repo to reproduce the issue. The readme.md file explains the repro and the issue.
It looks like Webpack is not executing/evaluating the return value of the loader.
Btw. this is not just the case with the css-loader. The result stays the same for raw-loader, sass-loader, style-loader, etc.
My final goal is to lazily load my theme files into a storybook.
I try to follow the documentation of the #etchteam/storybook-addon-css-variables-> theme.
Finally I got my issue solved!
Analysis
The main issue here is the webpack configuration generated by the #angular-devkit/build-angular package. I was able to analyze it by debugging a fresh angular12 application (you can check it out here).
By setting a break-point at /node_modules/#angular-devkit/build-angular/src/utils/webpack-browser-config.js, function: generateWebpackConfig(...), I could inspect the final webpackConfig object in the debugger.
The relevant rule looks like this:
The important part here is the rule setting the module type to asset/source, instructing webpack not to evaluate the loader's result.
Solution concept 1: inline loader
With the help of alexander-kait and his great hints at this issue,
I was able to find an inline-loader syntax that overrides webpack's module declaration:
import Test from 'test.css.webpack[javascript/auto]!=!!!style-loader?injectType=lazyStyleTag!css-loader!./test.css';
console.log(typeof Test); // output: object
console.log(Test); // output: Object { use: () => void, unuse: () => void }
Test.use(); // this should usually be called by a theme switcher...
I'm not really sure about the url pattern here, as it seems to be an undocumented feature, but I assume that it's something like <query-pattern>.webpack[<module-type>]!=!<loaders><query>.
However, since this is an undocumented feature, I was rather reluctant to use it.
Solution concept 2: webpackConfig customization
Since I'm in a storybook context, I decided to customize the webpack configuration according to the storybook documentation.
My solution requires to set up a naming convention (e.g. *.theme.css).
// .storybook/main.js
module.exports = {
webpackFinal: async (config) => {
// exclude *.theme.css from the *.css ruleset
config.module.rules.find(rule => '.css'.match(rule.test)).exclude = [ /\.(?:theme\.css)$/i ];
// add a rule for *.theme.css
config.module.rules.push({
test: /\.(?:theme\.css)$/i,
use: [
{ loader: 'style-loader', options: { injectType: 'lazyStyleTag' } },
'css-loader',
],
});
},
};
With these rules in place, I can now simply do the following:
// preview.js
import LightTheme from './light.theme.css';
import DarkTheme from './dark.theme.css';
setupThemeSwitcher(LightTheme, DarkTheme);
Please note that the setupThemeSwitcher function is just pseudocode merely there for the example. In reality I'm using the #etchteam/storybook-addon-css-variables-theme addon...
I had a very similar issue with storybook and this extension, except l’m loading .scss files.
I simply adapted solution 2 to suit my .scss case and it works like a charm.
I couldn’t make solution 1 to work, but as stated, it sounds hacky whereas solution 2 is cleaner in my opinion.
Thanks a lot for sharing this solution, I was struggling for hours.

require('template.jade') in react-starter-kit

Can someone maybe explain me, how this build-time require works?
https://github.com/kriasoft/react-starter-kit/blob/feature/redux/src/server.js#L89
They are requiring a jade template, which package or configuration allows this, I seem unable to find it myself.
const template = require('./views/index.jade')
I think is much more elegant then:
import jade from 'jade'
const template = jade.compile('./views/index.jade')
As RGraham mentioned in his comment, the require call is being "intercepted" during webpack's compilation of the application bundle. This is done using "loaders" that define particular behaviour for imports of a particular type:
Loaders allow you to preprocess files as you require() or “load” them.
In this particular case, the loader that does this modification could be one of these (or another that I didn't find in my search):
https://github.com/bline/jade-html-loader
https://github.com/webpack/jade-loader
Edit: looking at the project's own webpack configuration we can see it is the second link above:
{
test: /\.jade$/,
loader: 'jade-loader',
}
jade-loader reads the content of the specified file, which make look something like this (Jade string):
h1 Hello, #{author}!
..and replaces that with a CommonJS JavaScript code similar to this (at compile time):
module.exports = function(data) {
return `<h1>Hello, ${data.name}</h1>`;
};

Path aliases for imports in WebStorm

I use webpack path aliases for ES6 module loading.
E.g. If I define an alias for utils instead of something like
import Foo from "../../../utils/foo", I can do
import Foo from "utils/foo"
The problem is that once I start using aliases, WebStorm looses track of the import and I'm left with warnings and no auto-completion.
Is there a way to instruct WebStorm to use such aliases?
Yes, there is.
In fact, Webstorm can't automatically parse and apply Webpack config, but you can set up aliases the same way.
You just have to mark the parent folder of "utils" (in your example) as a resource root (right-click, mark directory as / resource root).
We just managed to do with the following structure :
/src
/A
/B
/C
We have A B and C folders declared as alias in Webpack.
And in Webstorm we marked "src" as "Resource Root".
And now we can simply import :
import A/path/to/any/file.js
instead of
import ../../../../../A/path/to/any/file.js
while still having Webstorm correctly parsing and indexing all code, link to files, autocompleting and so on ...
I managed to set up aliases for WebStorm 2017.2 within webpack like this:
For the record: in PHPSTORM, working with laravel mix, I managed to solve this by creating a webpack.config.js file separately like:
const path = require('path')
const webpack = require('webpack')
module.exports = {
...
resolve: {
extensions: ['.js', '.json', '.vue'],
alias: {
'~': path.resolve(__dirname, './resources/assets/js')
}
},
...
}
And then importing it in the webpack.mix.js like:
const config = require('./webpack.config')
...
mix.webpackConfig(config)
Make sure the webpack configuration file is pointed correctly in the configuration of the PhpStorm in: Settings > Languages & Frameworks > Javascript > Webpack
You can define custom paths, so WebStorm/PhpStorm can understand your aliases. But make sure, they are identical with your aliases. Create file in your root directory and call it something like this: webStorm.config.js (any js file will be ok). Then configure your paths inside:
System.config({
"paths": {
"components/*": "./src/components/*",
"core/*": "./src/core/*",
...
}
});
WebStorm/PhpStorm will recognize System as it's own module and will treat this file as configuration.
This is answered in a comment but to save people digging into comments and link only information, here it is:
As of WS2017.2 this will be done automatically. The information is here.
According to this, webstorm will automatically resolve aliases that are included within the webpack.config in the root of the project. If you have a custom structure and your webpack.config isn't in the root folder then go to Settings | Languages & Frameworks | JavaScript | Webpack and set the option to the config you require.
Note: Most setups have a base config which then call a dev or prod version. In order for this to work properly, you need to tell webstorm to use the dev one.
Not right now, We were also using path aliases for the files in our react project. The import names were shorter but we lost a lot on static checking of webstorm as well as completion features.
We later came up with a decision to reduce the code to only 3 levels of depth, as well a single level for the common parts. The path completion feature of webstom (ctrl + space) even helps reduce the typing overhead. The production build does not use longer names, so hardly makes any difference in final code.
I will suggest please reconsider your decision about aliases. You loose semantic meaning of modules coming from node_modules and your own code, as well as referencing the alias files again and again to make sense of your code, is a much bigger overhead.
add jsconfig.js on your project root
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
}
}
}
In PHPStorm (using 2017.2 currently), I have not been able to get webpack configs to work properly in regards to aliases.
My fix involves using the "Directories" section of the main settings. I just had to mark each folder referenced by an alias as a sources root, then click the properties dropdown for each and specify the alias as a "Package prefix". This made everything link up for me.
Not sure if the Directories section exists in WebStorm, but if it does, this seems to be a fool-proof method for getting import aliases working.
For anyone struggling: path.resolve() must be called with "__dirname" first argument for Idea (Websorm) to be able to resolve the path correctly.
Will work for Idea (Websorm):
alias: {
'#someAlias': pathLib.resolve(__dirname, 'path/to/directory')
}
Will not work for Idea (Websorm) (while still being valid webpack alias):
alias: {
'#someAlias': pathLib.resolve('path/to/directory')
}
Webstorm can't read webpack.config if module.exports return a function.
For example
module.exports = function (webpackEnv) {
return {
mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
...
}
}
Check your config file, maybe this cause you are a problem.
There is a lot of discussion here about Laravel Mix, so I'll leave this here to help out future readers. I solved this by creating a separate (fake) webpack config file which is only used by my IDE (PHPStorm).
1. Create a separate alias.js file (e.g. /webpack/alias.js)
const path = require('path');
const assets = path.join(__dirname,'..','resources','assets');
module.exports = {
'#js' : path.resolve(assets, 'js'),
'#c' : path.resolve(assets, 'js', 'components'),
'#errors' : path.resolve(assets, 'js', 'errors'),
'#utils' : path.resolve(assets, 'js', 'utils'),
'#store' : path.resolve(assets, 'js', 'store'),
'#api' : path.resolve(assets, 'js', 'api'),
'#less' : path.resolve(assets, 'less')
}
2. Require the alias.js file into webpack.mix.js
const mix = require('laravel-mix');
mix.alias(require('./webpack/alias'))
// ... The rest of your mix, e.g.
.js('app.js')
.vue()
.less('app.less');
3. Create the fake webpack config for your IDE (e.g. /webpack/ide.config.js)
Here, import the laravel-mix webpack config, plus your aliases, and any other config that the IDE might need help finding. Also include the prefixed ~ aliases for importing styles into your Vue components.
/*
|--------------------------------------------------------------------------
| A fake config file for PhpStorm to enable aliases
|--------------------------------------------------------------------------
|
| File > Settings... > Languages & Frameworks > Javascript > Webpack
|
| Select "Manually" and set the configuration file to this
|
*/
const path = require('path');
const mixConfig = require('./../node_modules/laravel-mix/setup/webpack.config')();
module.exports = {
...mixConfig,
resolve: {
alias: {
...require('./alias'),
'~#less' : path.resolve('#less'), // <--
},
...mixConfig.resolve
}
}
4. Set your IDE to use webpack/ide.config.js as your webpack config file.
Had the same problem on a new Laravel project with Jetstream. The webpack.config.js was present and correct. But PHPStorm still didn't recognize the # symbol as a resource root.
After opening the webpack config, I got a notification:
After Clicking on Trust project and run, the # symbol became recognized.
I know that this isn't the solution or use-case for everyone. But I still found it worthy to note on this post, because it helped me in my situation.
Using
laravel/framework:8.77.1
npm:8.3.0
node:v14.18.1

Resolving external submodule dependences in webpack

I am trying to include a modular third-party library (PhysicsJS) into my webpack project. This library is AMD- and CommonJS-friendly, and has well-formed submodules that I want to access. However it is primarily structured for RequireJS, via its packages definition spec in require.config(), so the entry point isn't a standard index.js. Instead the entry point is physicsjs.js.
In other words, I can't seem to figure out how to configure webpack to resolve both the library's main file and its submodules. It just seems like if the library's entry point isn't index.js and it has submodules, you are out of luck, and I just can't believe that's correct, so I must be missing something.
So, how can the following statements be made to resolve?
require('physicsjs'); // entry point
require('physicsjs/bodies/rectangle'); // submodule
I have tried variations of this config:
resolve: {
modulesDirectories: [
'js/bower_components'
],
alias: {
'physicsjs': 'PhysicsJS/dist/',
// doesn't find physicsjs.js
'physicsjs': 'PhysicsJS/dist/physicsjs.js'
// doesn't find the submodules
}
},
The directory structure looks something like this:
+ js
- main.js
+ bower_modules
+ PhysicsJS
+ dist
- physicsjs.js // module entry point
+ bodies
- rectangle.js // desired submodule
+ lib
- MyModule.js
Note that PhysicsJS does have a minified version of the entire library, which I will use if there is no other alternative, but I would rather only load what I actually use.
Also, the submodules themselves use require('physicsjs'), so calling require('physicsjs/physicsjs') is not a solution.
The solution is to declare the alias twice, first as an exact match (using a trailing $) and then again as a normal match (no trailing $). So my config now looks more like this:
resolve: {
modulesDirectories: [
'js/bower_components'
],
alias: {
'physicsjs$': 'PhysicsJS/dist/physicsjs.js', // Exact match
'physicsjs': 'PhysicsJS/dist' // and again with a fuzzy match
},
},

Categories

Resources