How can I make webpack treeshake my ES6 module? - javascript

I have followed every guide out there for turning on tree shaking in webpack 4 but webpack still bundles code from a NPM module that I have set up to be treeshaken. How can I enable treeshaking and not include particular code in my web app?
Full source code in GitHub repo
I have a CRA (create-react-app) that imports a NPM module I created to test it that looks like this:
package
index.mjs
dist
esm
components
image
index.js
index.js
index.js
with package.json like this:
"main": "index",
"module": "index.jsm",
"sideEffects": false,
that transpiles using babel with babelrc like:
{
"presets": [
[
"#babel/preset-env",
{
"modules": false
}
],
"#babel/react",
"#babel/flow"
]
}
I have 3 React components: image, avatar (that imports image) and login-button that has this es6 code:
import React from 'react'
document.body.innerHTML = document.body.innerHTML + "<div>This is a side effect and should never have happened</div>"
export default () => <button>Login</button>
The index file for the NPM es6 module:
import * as lib from './dist/esm'
export default lib
In the CRA's app.js I have:
import TreeshakingTestModule from 'treeshaking-test-module'
function App() {
return (
<div className="App">
<TreeshakingTestModule.Avatar />
</div>
);
}
When I npm start it builds and renders the avatar but I also see the message.
Note that login-button is never used however the default export of my NPM package which does export the component is imported, however I read tutorials that explained that even default exports and re-exports should still work with tree shaking if what is exported is never used.
What is happening? What am I doing wrong?
Full source code in GitHub repo

As far as I know, webpack three shakes by analysing imports statically, not by reading your code. You cannot expect webpack to realize you're only using Avatar from TreeshakingTestModule.
So you need to declare what you want to use at import level, by using named imports and exports:
import {Avatar} from 'treeshaking-test-module'
function App() {
return (
<div className="App">
<Avatar />
</div>
);
}
So webpack knows you're only using the Avatar export.
This means that Avatar needs to be a named export in your treeshaking-test-module module.

Related

React-Markdown Error: Must use import to load ES Module:

I try to install react-markdown to my nextjs project, but getting instantly following error when I try to use it.
My Code:
import React from 'react'
import ReactMarkdown from 'react-markdown'
export function Markdown({ markdown }: { markdown: string }) {
return (
<article className="prose-sm">
<ReactMarkdown>{markdown}</ReactMarkdown>
</article>
)
}
The error message:
Error: Must use import to load ES Module: /Users/username/Projects/mono/node_modules/react-markdown/index.js
require() of ES modules is not supported.
require() of /Users/username/Projects/mono/node_modules/react-markdown/index.js from /Users/username/Projects/mono/dist/apps/webapp/.next/server/pages/_app.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename index.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /Users/username/Projects/mono/node_modules/react-markdown/package.json
I am using following Versions:
Node on v14.17.5 and yarn 1.22.11 and my current Nextjs version is ^12.6.2
I found following solution on Github. Here
You need to add next-transpile-modules to your next.config.js like following.
// next.config.js
const withTM = require('next-transpile-modules')(['react-markdown']);
module.exports = withTM({
...
})
and you need to import react-markdown like:
import ReactMarkdown from 'react-markdown/react-markdown.min';

Importing self-made library into vue project

Intro: I've generated two projects with vue-cli ~4.2.0: parent-app and dummylib
Goal: to create DummyButton.vue component in dummylib project and import it in parent-app project.
What I've done:
Followed this tutorial.
In dummylib's package.json I've inserted:
"main": "./dist/dummylib.common.js",
and build-lib script:
"build-lib": "vue-cli-service build --target lib --name dummylib src/main.js",
dummylib's main.js:
import DummyButton from './components/DummyButton.vue'
export default DummyButton
Also I've created DummyButton.vue and now vue serve src/components/DummyButton.vue successfully renders DummyButton component and npm run build-lib generates dist folder with dummylib.common.js
In parent-app project I've made npm i ../dummylib and it has been added to package.json:
"dependencies": {
...
"dummylib": "file:../dummylib",
...
},
Problem:
When I try to start parent-app with npm run serve a lot of linting errors occurs in ../dummylib/dist/dummylib.common.js. As far as i understand, ESlint should not even try to process dummylib.common.js, but it does and it results in ~2000 errors.
I tried the same tutorial a while ago. To fix it: in the main.js of the library I had to do has to do this:
const Components = {
DummyButton
};
Object.keys(Components).forEach(name => {
Vue.component(name, Components[name]);
});
export default Components;
instead of
export default DummyButton
Also did you remember to import the lib into your parents apps main.js
import DummyLib from "DummyLib";
Vue.use(DummyLib);
You can also import straight into a component like so:
import DummyLib from "DummyLib";
export default {
components: {
...DummyLib
},
//... rest of vue file script ...
}

Angular class is not an Angular module

When I build my Angular library, publish it to npm, and use it as a dependency in another project, whenever I try to import on of my module classes into my app.module.ts, and get this error Class TekButtonModule is not an Angular module. I have followed steps from multiple different sites on how to create, build, and publish angular libraries, and I can't figure out why it won't recognize the class as a valid module class.
In my app.module.ts, this is how I am importing the module:
import { TekButtonModule } from "tek-angular-uimodules";
#NgModule({
imports: [
TekButtonModule
]
})
export class AppModule { }
My library project follows the standard directory structure outlined by Angular. I did not change anything with the build, or path settings when I setup the library project. My built library project has a bundles, esm5, esm2015, fesm5, fesm2015, lib (where my custom modules, and components are), as well as a package.json, public-api.d.ts (exports everything in the lib directory), README.md, tek-angular-uimodules.d.ts (exports the public api), and a tek-angular-uimodules.metadata.json file.
Is there some extra configuration that it needed that isn't setup by default to get modules to work correctly?
Here is my button module class:
import { CommonModule } from "#angular/common";
import { NgModule } from "#angular/core";
import { TekButton } from "./button";
#NgModule({
declarations: [
TekButton
],
exports: [
TekButton
],
imports: [
CommonModule
]
})
export class TekButtonModule {}
Here are some of the files that are generated when I build the project:
tek-angular-uimodules.d.ts:
/**
* Generated bundle index. Do not edit.
*/
export * from './public-api';
public-api.ts
export * from "./lib/button";
export * from "./lib/button-group";
export * from "./lib/nav-bar";
./lib/button/button-module.d.ts
export declare class TekButtonModule {
}
If I import the generated javascript module file from the esm5 directory manually in my app.module.ts file, then it works just fine. But I have to manually do that, when it should just work with the standard module import that WebStorm auto imports for me like any other package.
This is the generated module js file under the esm5 directory:
/**
* #fileoverview added by tsickle
* #suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
import { CommonModule } from "#angular/common";
import { NgModule } from "#angular/core";
import { TekButton } from "./button";
var TekButtonModule = /** #class */ (function () {
function TekButtonModule() {
}
TekButtonModule.decorators = [
{ type: NgModule, args: [{
declarations: [
TekButton
],
exports: [
TekButton
],
imports: [
CommonModule
]
},] }
];
return TekButtonModule;
}());
export { TekButtonModule };
//# sourceMappingURL=data:application/json;base64
Any help is appreciated. I can provide more code, and screenshots if needed.
This is the error I get when trying to import my module:
So I figured out what was causing my specific issue. It was two things. Point two is the direct reason for my issue, point one was making much more confusing to debug.
In order to test my built package, I would run ng build, and then cd into the built project in the dist directory, and run npm pack. I would then install my built package as a file dependency in an entirely separate project to ensure I did everything correctly. What I found out was (or I'm assuming) that there was a caching mechanism going on even when doing local file dependency installations. This caching seemed to be tied to the file name of the tgz file that is generated from npm pack. This caching was causing the very first version to be constantly reinstalled not matter how many changes I made to the built library. The fix, for me, was to simply rename the tgz file to something different each time. You should also change the library version number, and that should also work.
My file structure looked like this:
src
- public-api.ts
- lib
-- button
--- index.ts
--- public-api.ts
--- button.component.ts
--- button.module.ts
-- another-module
-- yet-another-module
The public-api.ts file directly in src looked like this:
export * from './lib/button'
// Other exports
The public-api.ts, and the index.ts file under src\lib\button looked like this:
// public-api.ts
export * from './button.component.ts'
export * from './button.module.ts'
// index.ts
export * from './public-api.ts'
I had assumed that by adding the index.ts files to each directory, the export line in the root public-api.ts would find the index file, and export the files accordingly, but somewhere along the line, this implicit resolution does not work, and, instead, an explicit export is needed. The fix was to change export * from './lib/button' to export * from './lib/button/index.ts' or export * from './lib/button/public-api.ts'.
Point two was the direct reason for my issue, point one just made it really confusing when trying to debug.
So I had the same issue with a different solution, so I thought I would share it here.
I had just updated my library to Angular 9. And everything seemed fine except the strange "Class my-module is not an Angular module" error.
What worked for me was to up tsconfig.lib.ts file with:
...
"angularCompilerOptions": {
...,
"enableIvy": false
}
Don't know if this will help anyone, but for me it was caused by the NgModule import path autocompleting with #angular/core/src/metadata/*, rather than #angular/core.

Critical dependency warning when using react-pdf

I'm trying to display a pdf on a react application and i get the following warning:
/node_modules/react-pdf/node_modules/pdfjs-dist/build/pdf.js
Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
Vscode tells me this under the import function.
Could not find a declaration file for module 'react-pdf'
Already tried running npm install, npm install react-pdf and reinstalling the package
import React, { Component } from 'react';
import { Document } from 'react-pdf';
import sample from 'file location'
export default class viewer extends Component {
render() {
return (
<div>
<Document
file={sample}
onLoadSuccess={this.onDocumentLoadSuccess}
>
</Document>
</div>
);
}
}
Displays:
"Failed to load PDF file" in the browser
This code will display your pdf file, but issue will display in IDE console.
import { Document, Page, pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
In my case I'm using webpack 4 and it's not supportted yet.
If you build project it will work fine.
My full workaround.
Create a file at the root called config-overrides.js and it should contain the following:
module.exports = function override(config) {
config.module.rules[0].parser.requireEnsure = true
return config
};
After that npm i react-app-rewired to you app and change your build function in package.json to read react-app-rewired build/react-app-rewired start.
That should do it for now.

Auto-completion in Webstorm for my custom npm module (ES6/Babel)

When I use material-ui package I get nice auto-completion in Webstorm (ctrl+space):
I thought it might have something to do with the fact the package includes an index.es.js file:
import _AppBar from './AppBar';
export { _AppBar as AppBar };
import _AutoComplete from './AutoComplete';
export { _AutoComplete as AutoComplete };
import _Avatar from './Avatar';
export { _Avatar as Avatar };
import _Badge from './Badge';
export { _Badge as Badge };
import _BottomNavigation from './BottomNavigation';
...
So I generated my own index.es.js in my custom npm module and put it next to the transpiled index.js:
import {ActionTypes as _ActionTypesElements} from './reducers/elements/elements';
export { _ActionTypesElements as ActionTypes };
import {ActionTypes as _ActionTypesAppState} from './reducers/appState/appState';
export { _ActionTypesAppState as ActionTypesAppState };
import _appStateActions from './reducers/appState/appState_actions';
export { _appStateActions as appStateActions };
...
And yet I get no auto-complete:
Any idea why?
Found the answer:
Had to add a jsnext:main field to the package.json of the npm module:
package.json:
...
"module": "./index.js",
"jsnext:main": "./index.es.js",
...
Webstorm recognizes the package's inner exports.
In WebStorm 2019.3, here are the steps I follow to force Code Completion (including auto-import) to work for a custom, self-published NPM package:
Ensure that the project, itself, has a package.json file at the root of the project, and that package.json includes the desire package in the "dependency" object. For example:
{
"name": "testproject",
"version": "1.0.0",
"dependencies": {
"#yourname/yourpackage": "latest"
}
}
In WebStorm, select File > Invalidate Caches / Restart...
To enable auto-import for package contents, ensure that the JavaScript file in which the package is being used has AT LEAST ONE export statement. For example, in the following code, an export is present, so Code Completion auto-imports the package function isNil():
export function init () {
isNil
}
By comparison, the following code does not contain an export statement, so isNil() is not automatically imported:
function init () {
isNil
}
For me, all three of the preceding steps are necessary for Code Completion to work for my own NPM packages in WebStorm.

Categories

Resources