Search code examples
react-nativenpmimportpackageexport

npm package export path definitions


I'm generating my first set of npm packages to support a react-native environment. I am looking to configure the packages in such a way that the contents is intrinsically organized and can be easily distinguished by import statements on the client end.

Given 4 function definitions defined in my npm package (package name: super-cool-animals):

snakes.ts

export function Boa() {}
export function Anaconda() {}

birds.ts

export function Pigeon() {}
export function Hawk() {}

I would like the consumer of the library to be able to install the library:

npm install --save super-cool-animals

And then import them as such:

import { Boa, Anaconda } from 'super-cool-animals/snakes'
import { Pigeon, Hawk } from 'super-cool-animals/birds'

Pigeon();  // direct access to the function!

I'm having a hard time identifying the proper mechanism to achieve this, and I believe I've seen this mechanism in some environments (ie. angular)

I've seen some suggestions allowing you to combine them into a single variable as a for instance by using index.ts files:

snakes/index.ts

export * from './snakes.ts'

birds/index.ts

export * from './birds.ts

./index.ts

import * as Birds from './birds'
import * as Snakes from './snakes'
export { Birds, Snakes}

But this has an ugly end result, you now have to reference the container to get at the function:

Consumer of the library:

import { Birds } from 'super-cool-animals';

Birds.Pigeon();

Any assistance is appreciated. Thank you.


Solution

  • Although technically speaking the "Original Answer" allows you to do the imports in the fashion described above, I think it is important to note that it is not for the reason I thought it was.

    the reason import Boa from 'super-cool-animals/snakes was working was simply because I was not minifying the compiled output. This caused the source folder structure / file names to be preserved in the compiled output and then the library.

    The import statement simply lets you traverse the libraries file system. Since there was an index.js file under /snakes it provided access to those functions.

    So it is a partial answer in that you can achieve that style of import, but I'm not sure I would suggest it, as it implies that you have to keep the source's files and folder structure the same otherwise it could cause the library to break for the caller. (ie. they imported from a folder/file you didn't intend them to and then you renamed that internal folder/file later)

    ========== Original Answer ============

    Turns out the answer was pretty simple, lets take our example hypothetical where you want each category broken out by animal type, but you only want one package to be installed.

    > super-cool-animals
        > snakes
            anaconda.js
            boa.js
            package.json
            index.js
        > birds
            hawk.js
            pigeon.js
            package.json
            index.js
        package.json
        readme.md
        index.js
    

    each index file simply does an export of the files or functions of interest at that level

    index.js

    import * as Birds from './birds'
    import * as Snakes from './snakes'
    export { Birds, Snakes}
    

    snakes/index.js

    export * from './anaconda.js'
    export * from './boa.js'
    

    birds/index.js

    export * from './hawk.js'
    export * from './pigeon.js'
    

    Now moving on to the package.json files: the top level package file describes any dependencies, author, name of the library to be used in importing etc. The animal - specific - package.json files simply describe the name of the library to import.

    package.json

    {
      "name": "super-cool-animals",
      "version": "1.0.0",
      "description": "Super cool animal library",
      "main": "index.js",
      "author": "Kevin Quinn <[email protected]>",
      "license": "MIT",
      "dependencies": {},
      "peerDependencies": {},
      "devDependencies": {}
    }
    

    The package files found at the lower level (birds and snakes folder) are simply describing the available relative path.

    snakes/package.json

    {
      "name": "super-cool-animals/snakes",
      "main": "index.js"
    }
    

    birds/package.json

    {
      "name": "super-cool-animals/birds",
      "main": "index.js"
    }
    

    With all that defined, you can now install the npm package, and the client can nicely define their imports:

    import { Hawk } from 'super-cool-animals/birds';
    const SomeBird = Hawk();
    

    Not sure if this is the best solution, but it seemed to function for me, I hope it is helpful to someone, and if there is a better or cleaner solution please post it and I will be happy to accept a better answer, or update my own answer.