I've built a relatively small NPM package consisting of roughly 5 different ES6 classes contained in one file each, they all look pretty much like this:
export default class MyClass { // ... }
I've then setup an entry point for my package that looks like this:
export { default as MyClass } from './my-class.js'; export { default as MyOtherClass } from './my-other-class.js';
I've then run the entry point through webpack and babel ending up with a transpiled and minified index.js
Installing and importing the package works fine, but when I do the following from my client code:
import { MyClass } from 'my-package';
It doesn't just import "MyClass" it imports the entire file including all dependencies of every class (some of my classes have huge dependencies).
I figured this is how webpack works when you try to import parts of an already bundled package? So I set up my local webpack config to run node_modules/my-package
through babel too and then tried:
import { MyClass } from 'my-package/src/index.js';
But even this imports every single class exported by index.js. The only thing that seems to work the way I want is if I do:
import MyClass from 'my-package/src/my-class.js';
But I'd much rather:
What's the best practice here? How do others achieve similar setups? I've noticed GlideJS has an ESM version of its package which allows you to import only the things you need without having to run babel through it for example.
The package in question: https://github.com/powerbuoy/sleek-ui
webpack.config.js
const path = require('path'); module.exports = { entry: { 'sleek-ui': './src/js/sleek-ui.js' }, output: { filename: '[name].js', path: path.resolve(__dirname, 'dist'), library: 'sleek-ui', // NOTE: Before adding this and libraryTarget I got errors saying "MyClass() is not a constructor" for some reason... libraryTarget: 'umd' }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } ] } ] } };
package.json
"name": "sleek-ui", "version": "1.0.0", "description": "Lightweight SASS and JS library for common UI elements", "main": "dist/sleek-ui.js", "sideEffects": false, // NOTE: Added this from Abhishek's article but it changed nothing for me :/ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --mode production" }, "repository": { "type": "git", "url": "git+https://github.com/powerbuoy/sleek-ui.git" }, "author": "Andreas Lagerkvist", "license": "GPL-2.0-or-later", "bugs": { "url": "https://github.com/powerbuoy/sleek-ui/issues" }, "homepage": "https://github.com/powerbuoy/sleek-ui#readme", "devDependencies": { "@babel/core": "^7.8.6", "@babel/preset-env": "^7.8.6", "babel-loader": "^8.0.6", "webpack": "^4.42.0", "webpack-cli": "^3.3.11" }, "dependencies": { "@glidejs/glide": "^3.4.1", "normalize.css": "^8.0.1" } }
You can export as many functions as needed as long as you remember that there can be only one default export. The default export in JavaScript is used to export a single/fallback value from a module. With a default export, you do not need to specify a name for the exported function.
There are two different types of export, named and default. You can have multiple named exports per module but only one default export.
Use named exports to export multiple classes in JavaScript, e.g. export class A {} and export class B {} . The exported classes can be imported by using a named import as import {A, B} from './another-file. js' . You can have as many named exports as necessary in a file.
I figured this is how webpack works when you try to import parts of an already bundled package?
Yes, the way you've got it set up is importing every class in index.js, which is then transpiled into one file (if it's targeting ES5, which is most common*). This means that when that file is imported in another file it comes in its entirety, with all those classes.
If you want proper tree-shaking you should avoid transpiling it into a CommonJS (ES5) bundle. My suggestion is to keep the ES6 modules, either by itself or in a separate location from the ES5 bundle. This article should help you fully understand this and has recommended instructions. Essentially it boils down to setting the Babel environment using preset-env (highly recommended if you're not already using it!) to preserve ES6 syntax. Here's the relevant Babel configuration, if you don't want to transpile to ES5:
{ "presets": [ [ "@babel/preset-env", { "targets": { "esmodules": true } } ] ] }
The article details how to set up 2 bundles, each using a different module syntax.
Also worth noting, and is kind of mentioned in the article as well, you can set the ES module entry point in package.json. That tells Webpack/Babel where the ES6 modules can be found, which may be all you need for your use case. It seems conventional wisdom says to do:
{ "main": "dist/sleek-ui.js", "module": "src/main.js" }
But Node documentation has it as:
{ "type": "module", "main": "dist/sleek-ui.js", "exports": { ".": "dist/sleek-ui.js", "./module": "src/main.js" } }
If I had time I would play around with this and see which works correctly, but this should be enough to set you on the right path.
*ES5-targeted bundles are in CommonJS format, which has to include all the associated files, because ES5 doesn't have native module support. That came in ES2015/ES6.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With