Import from subfolder of npm package
Asked Answered
P

6

55

I've been working on creating a small library of React components for use in several other projects. I am publishing the package internally (using a private GitHub repository) and then including in another project. However, when I go to import from a subdirectory of the package I am not able to do so as the paths don't match.

The projects using the package all utilize webpack to bundle/transpile code as I am trying to avoid doing any building in the component library if possible.

Directory Structure

- package.json
- src/
  - index.js
  - Button/
    - index.js
    - Button.jsx
    - ButtonGroup.jsx
  - Header/
    - index.js
    - Header.jsx (default export)

package.json

...
"main": "./src/index.js",
"scripts": "",
...

src/Button/index.js

import Button from './Button';
import ButtonGroup from './ButtonGroup';

export default Button;

export { Button, ButtonGroup};

src/index.js

Is this file actually necessary if only importing from subdirectories?

import Button from './Button';
import ButtonGroup from './Button/ButtonGroup';
import Header from './Header';

export { Button, ButtonGroup, Header };

Other Project

// This project is responsible for building/transpiling after importing
import { Button, ButtonGroup } from 'components-library/Button';

Example

Material-UI is a library of React components that is used by requiring in the following fashion: import { RadioButtonGroup } from 'material-ui/RadioButton. I've tried to figure out how this works for them but to no avail yet.

Similar Questions

Questions

  1. Can I skip the src/ directory somehow in the import path?
  2. Can I skip any type of build phase in the package (so developers don't have to build before committing)?
  3. How does a package similar to material-ui handle this?
Protolanguage answered 3/6, 2017 at 14:59 Comment(5)
Did you discover how Material-UI achieves the import npm-package/subfolder/Component way to import a src/subfolder/Component component?Interview
I eventually moved on from this to other alternatives, sorry.Protolanguage
Could you share your experience, please? I'm currently looking for something similar and couldn't find useful resources. Right now I am packing a library I'm writing without the src directory (github.com/tonix-tuft/react-js-utl). I create subfolders with ES6 source code in the root of the project and publish those dirs to NPM, but I guess this is not optimal (e.g. ES6 exports do not work server-side in Node)... This way though, I am able to import the needed files using the react-js-utl/subfolder syntax. By the way, I wish you a happy new year! Thank you.Interview
Unfortunately I do not remember what I wound up doing, as it has been almost a year and a half :(. I do wish there were resources on this, but do remember that I wasn't able to figure it out soon enough and had to move on.Protolanguage
you can this my anwser https://mcmap.net/q/334454/-import-from-subfolder-of-npm-packageBandage
O
32

Can I skip the src/ directory somehow in the import path?

Yes. Using the package.json "exports" field, which should be supported by Webpack in a near future (see this issue), but has already been supported by Node since Node 12 LTS following the Bare Module Specifier Resolution proposal:

package.json

...
"main": "./src/index.js",
"type": "module",
...
"exports": {
  "./Button": "./src/Button/index.js",
  "./Header": "./src/Header/index.js"
},
...

Now, the following code:

// This project is responsible for building/transpiling after importing
import { Button, ButtonGroup } from 'components-library/Button';

should be translated to:

import { Button, ButtonGroup } from 'components-library/src/Button/index.js';

which should correctly import the requested modules.

Caveat

Now, it would certainly be tempting to try a simpler version like:

...
"exports": {
  "./Button": "./src/Button/",
  "./Header": "./src/Header/"
},
...

so as the usual import statement

import { ... } from 'components-library/Button';

gets translated to

import { ... } from 'components-library/src/Button';

This looks nice, but it will not work in this case, because your submodules don't have each their own package.json file but rely on their index.js file to be found.

/!\ Unlike in CommonJS, there is no automatic searching for index.js or index.mjs or for file extensions.


src/index.js - Is this file actually necessary if only importing from subdirectories?

I don't think so, but you can keep it if you want.


Can I skip any type of build phase in the package?

Using the "exports" field does not require you to transpile your code.

Opiumism answered 15/5, 2020 at 22:26 Comment(3)
Good answer. I found more information here: medium.com/swlh/npm-new-package-json-exports-field-1a7d1f489ccfCreamcolored
Typescript users: github.com/microsoft/TypeScript/issues/33079Creamcolored
Excellent answer! I just came back to this (years later!) with a similar requirement, and was happy to see this answer, as I did not realize exports could use multiple files!Protolanguage
T
2

The answer may depend on how you installed your components library. If you did it via either npm install <git-host>:<git-user>/<repo-name> or npm install <git repo url>,

  1. You should be able to import {Button} from 'component-library/Button' as is, according to your first linked question. Similar to Node's require() resolution, Webpack should resolve subdirectories within component-library relative to component-library's entry point. You can find the docs on customizing the resolution behavior via the webpack.config.resolve property. material-ui seems to rely on resolving subdirectory imports from the module entry directory.

  2. To distribute an ES module library, there's no need for building before distribution. However, projects such as create-react-app may need a pre-transpiled version.

Alternately, you can write import {Button} from 'components-library'. Webpack will trace the dependencies back through each index without a fuss.

Twophase answered 11/4, 2018 at 18:13 Comment(0)
T
2

One of the possible solutions there is webpack aliasing system.
You can create another project, call it for example 'app-aliases', so your aliases will be reusable.
This project will has one js file with all of your packages paths:

const path = require('path');

module.exports = {
'@components': path.resolve(__dirname, 'node_modules/components-library/src'),
'@another': path.resolve(__dirname, 'node_modules/any/path/you/want'),
}

And then add it to the webpack configuration in any project which will be responsible for building/transpiling:
webpack.config.js

const appAliases = require('app-aliases');

const config = {
...
  resolve: {
    alias: {
      ...appAlises
    }
  }
}

In the runtime code you will be able to use it like this:

import {Button} from '@components/Button';
import {Something} from '@another'

If you are using typescript you will need to add the same aliases to the paths tsconfig property.
So answers to your questions are:

  1. Yes, you can use any path in aliases
  2. Yes, it is not necessary to build all of your projects
  3. I see that now mui uses imports from directi packages (core for example), see https://material-ui.com/components/radio-buttons/ there is import Radio from '@material-ui/core/Radio';. But I hope they using re-export that I described below.

Also about node.js resolution mechanism.
When you import some library it tries to find node_modules/some-library/package.json and then main property inside it. This property should lead to your main entry point. Usually it is src/index.js (you should set it in package.json if it is no there yet). In this file you can re-export anything you want from internals file structure and will be able to use it without the full path. Please see this repo for some examples.

Ta answered 14/5, 2020 at 9:5 Comment(1)
I can't take the time to verify this in the context of the original answer (since I no longer have the project); however, it does appear to match what I now know about Webpack aliases, etc.Protolanguage
L
1

I'am an angular developer never used react but what I could tell that material-ui are using monorepo where same concept exists in angular where we create one workspace and this workspace hold multiple project/packages as named in react. for more info Workspaces with Yarn

Material-ui using fake paths in tsconfig to make it appears like src folder doesn't exists this from the git you provided: tsconfig.json

Linguiform answered 15/5, 2020 at 17:40 Comment(2)
Does this answer address the user's question? You may want to bring your skills and experience to questions tagged with angular.Retroact
Yes, If you follow my answer you could create a project with the same structure as martial-ui project. What they did is a workspace containing multiple packages this is called monorepo project structure. second part of the question where you could import you packages as import { componentName} from '@company/workspaceName' this is done in tsconfig.json and gave him the axcample from material-ui as follow: "paths": { "@material-ui/core": ["./packages/material-ui/src"], "@material-ui/core/*": ["./packages/material-ui/src/*"], .... } this is called creating fake path.Linguiform
B
1

you have to install babel-plugin-module-resolver package

Specify the package relative path in your .babelrc file alias like this

{
  "plugins": [
    ["module-resolver", {
      "alias": {
        "components-library": "./node_module/components-library"
      }
    }]
  ]
}

then you can import subdir of npm package like this

import { Button, ButtonGroup } from 'components-library/Button';
Bandage answered 15/5, 2020 at 22:58 Comment(0)
A
0

This is possible but requires publishing a curated dist folder rather then the root of your project.

The whole thing is rather simple if you understand how module resolution works, and you just need a small script to prepare your distribution.

Lest I repeat all the details here, please see my answer for Importing from subfolders for a javascript package.

Assessor answered 8/3, 2021 at 21:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.