NPM package: best practices and exposing multiple import paths - javascript

I created an NPM package that uses Webpack and Babel for transpiling/bundling.
In my package.json, I've got main set to "main": "build/index.js". And in my Webpack config, I have entry set to entry: { app: './src/index.js' }. My entry file is shown below.
Everything works fine when the package is installed. However, with this setup, two import paths are exposed for every helper:
This is a problem for editors that support auto imports, since they will sometimes auto import from 'my-package/build/utils/helper1' rather than the preferred path of 'my-package'.
So, two questions:
Is there any way to prevent the longer import path from being exposed?
What is considered best practice when creating NPM packages. Is my setup acceptable, or should I be doing something different?
Entry File:
import helper1 from './utils/helper1';
import helper2 from './utils/helper2';
export {
helper1,
helper2,
};
const myPackage = {
helper1,
helper2,
};
export default myPackage;

you can utilize Webpack resolve
I often use the first way:
export {
helper1,
helper2,
};
Recently, I found that we can use Object.freeze() to export. This is a good article.

I would suggest probably merge your helper1 and helper2 in one file and name it helpers, then you can put them in the class myPackage so that you then export them as a module like this
import myPackage from './utils/helper';
// OR import {helper1, helper2} from './utils/helpers';
export default class myPackage {
helper1,
helper2,
};
OR
import {myPackage} from './utils/helpers';
// OR import {helper1, helper2} from './utils/helpers';
module.exports.myPackage = (helper1, helper2) => {
this.helper1 = helper1;
this.helper2 = helper2;
};
I hope this helps.

Related

module.exports does not contain a default export

I am trying to export my theme in my react app using `module.exports =
module.exports = {
defaultFontColor: "#282828",
name: "Planswell",
primary: primary,
deemphasizedPrimary: "#e7f6ee",
secondary: "#E4A432",
danger: "#DF5F2B"...}`
in my file whatever.js, and i try to import it in another file using import whatever from "themes/whatever.js";
All was working well, but i updated Babel, and am now getting the error Attempted import error: 'themes/whatever.js' does not contain a default export (imported as 'whatever').
What changed with Babel that caused this error? And how do I fix it?
If the only export in your whatever.js is
module.exports = {mod1, mod2, ...}
Then, assuming whatever is actually a module in your file, you should have never been able to import it using
import whatever from "themes/whatever.js";
The only way that would be possible is if in your whatever.js you did:
export default whatever;
Otherwise, you will have to destructure the import like so:
import {whatever, mod1, mod2, ...} from "themes/whatever.js";
Again, all this assumes that whatever is actually a module inside your whatever.js file e.g const whatever = () => {.... You don't make that part clear.
The error you're getting should help you guide your way. When using module.exports = {...} syntax, the default export is the object.
You should try importing specific exported properties of the module such as import { someModule } from 'themes/whatever.js'. Optionally you can use
import * as whatever from 'themes/whatever.js'
// Use it
whatever.myFunction()
Babel is pretty complex tool so I would check from which version you upgraded to and then looked at the change log to see what has changed. Babel has plethora of presets and plugins so it could be any combination, sorry no simple answer here.
To me it seems like perhaps you're using some different type of module.
Maybe you are using #babel/preset-env with combination of browserslist settings and you transpile to ES6 modules?
in your whatever.js you have exporting the as an object that contain your component so you have to export it like this
import {whatever} from "themes/whatever.js";
for your example to work you have to export your component without brackets
export {whatever}

Global Import In ES6

I have a large third party library that I need to share between two projects. The project has multiple folders with multiple files that contain multiple exports. Instead of importing these modules like this
import {BaseContainer} from '#company/customproject/src/containers/BaseContainer.js'
I would like to do this
import { BaseContainer } from '#company/customproject'
I know I can manually import all the modules into a single index.js file in the base directory but i am wondering if there is an easier way to do not have import them all explicitly
I know I can manually import all the modules into a single index.js file in the base directory but i am wondering if there is an easier way to do not have import them all explicitly
You should really just create an index.js file and import into that whatever you want to export so that you can control what APIs get exported and to not export private APIs.
That said there is an automated tool that generates an index.js automatically for you:
> npm install -g create-index
> create-index ./src
Which will generate an index.js with all the exports.
As the other answer suggests, you should create an index.js within each directory and explicitly export contents
#company/customproject/index.js
import {BaseContainer, SomeOtherContainer} from './src/containers'
export {
BaseContainer,
SomeOtherContainer
}
#company/customproject/src/containers/index.js
import BaseContainer from './BaseContainer'
import SomeOtherContainer from './SomeOtherContainer'
export {
BaseContainer,
SomeOtherContainer
}
Another option to autoload an entire directory is using require and module.exports to export every scanned file, however. You would likely run into conflicts using both ES6 import/export along with module.exports and default export statements.
#company/customproject/index.js
const fs = require('fs')
const modules = {}
fs.readdirSync(__dirname+'/src/containers').forEach(file => {
file = file.replace('.js', '')
modules[file] = require('./src/containers/'+file)
// map default export statement
if (modules[file].default) {
modules[file] = modules[file].default
}
})
module.exports = modules
Then simply use it in any ES5 or ES6 module
const {BaseContainer} = require('#company/customproject')
or
import {BaseContainer} from '#company/customproject'

Properly export/import parts of a JS project

I'm building an npm package (ES6 + Babel) for the first time and I'm having trouble connecting it all together so it can be imported by the end user.
My build (output) folder structure is the same as src:
build
- index.js
- BaseClass.js
sublclasses
- SubClassA.js
- SubClassB.js
SubClassA and SubClassB import and extend BaseClass and are both exported using the module.exports. The entry point, index.js, has only two lines:
import SubClassA from './subclasses/SubClassA'
import SubClassB from './subclasses/SubClassB'
package.json has the main field set to ./build/index.js.
When installing the project (or npm linking) into a test project, I write:
import SubClassA, SubClassB from 'my-package'
Import works, but imported classes are undefined. I've tried a couple more ways to do it, but it didn't work.
How should I do it properly?
EDIT: after changing index.js to:
import SubClassA from './subclasses/SubClassA'
import SubClassB from './subclasses/SubClassB'
module.exports = SubClassA
module.exports = SubClassB
it kind of works. 'Kind of' means that if I import both classes in the test project like so:
import SubClassA, SubClassB from 'my-package'
and then do:
let sca = new SubClassA()
it turns out to be SubClassB. If I ommit SubClassB from import, it works normally.
EDIT 2 - SOLUTION:
Per instructions in the comments below, I've changed the index.js file like so:
export { default as SubClassA } from './subclasses/SubClassA'
export { default as SubClassB } from './subclasses/SubClassB'
and I imported it in the test project like so:
import { SubClassA, SubClassB } from 'my-project' and it worked.
The problem is you're not exporting anything from your main file,
using es6 import/export syntax you can directly export it with:
export {default as SubclassA} from './subclasses/SubClassA'
export {default as SubclassB} from './subclasses/SubClassB'
then to you can use the named imports :
{SubClassA, SubClassB} from 'my-package'

React, webpack and creating a bundle using a per build json?

We would like to create bundle.js from React and webpack, where a per build configuration is included and made available to the React code, but we are not sure how to go about doing this.
The idea would be to be to do something like:
npm run build -- config="config123.json"
And then have the generated bundle.js include that configuration, such that it could be used by the root container. Something like:
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
ReactDOM.render(
<RootContainer config={configPassedByBuildProcess}/>,
document.getElementById('app')
);
Is this possible and if so, how should we approach this?
No, I don't believe that you can pass a file on build, from what I know the only thing that you can pass is a string like an ENV variable. You could import the config123.json file like this
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
import config from '/path/to/config123.json'
ReactDOM.render(
<RootContainer config={config}/>,
document.getElementById('app')
);
You may need a JSON loader for importing JSON files
After a bit of experimenting, the solution I have found is to use Webpack's DefinePlugin, since this provides a way of doing string substitution during webpack's build phase.
In my React code I defined:
// Will be substituted by webpack's DefinePlugin. Not an error.
const bundleConfig = BUNDLE_CONFIG;
and then in my webpack.config.js I first load my config (hard coded for now):
// The path can be pulled in via 'process.args' and parsed as appropriate
const bundleConfig = fs.readFileSync('bundle-config/default-config.json', 'UTF-8');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Note that if for some reason we want to build with a missing or undefined bundleConfig, then the right substitution would likely be the string (not the keyword) 'undefined' , to keep the code valid, such that:
const bundleConfig = 'undefined';
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Just one extra thing was the configuration of the package.json (scripts section only):
{
"scripts": {
"build": "run() { NODE_ENV=production && webpack -p $1; }; run",
}
}
This allow passing in the path to the webpack.conf.js script, when we call:
npm run build /path/to/config.json

How to fix eslint errors in meteor with react tutorial

I've followed meteor tutorial, and when I finished I've decided to install eslint.
Now I see
Prefer default export import/prefer-default-export
for this line: export const Tasks = new Mongo.Collection('tasks'); in imports/api/tasks.js file. It contains also some Meteor methods. Here it is full source code: tasks.js.
I was trying to fix this eg. with
const Tasks = new Mongo.Collection('tasks');
export { Tasks as default };
But then browser stopped rendering the view.
Here is the server/main.js content, which imports tasks.js:
import '../imports/api/tasks.js';
How can I fix lint error without breaking applications functionality?
You could add an .eslintrc file to your project root and adapt the rule:
{"rules": {"import/prefer-default-export": ["off"]}}
UPDATE:
If you want to keep the rule, then you need to export Tasks as default like so:
const Tasks = new Mongo.Collection('tasks');
export default Tasks;
Now you have to change all the imports in the rest of your codebase from a named import to a default import. The named import looks like this
import { Tasks } from '/imports/api/tasks';
see e.g. here, whereas the new default import has to look like this
import Tasks from '/imports/api/tasks';
This should do it. Let me know if it works for you.

Categories