简体   繁体   中英

How to import lodash in a typescript library package?

I'd like to centralize my generic most-used (typescript) functions in a Util package which I can reuse across my projects. Turned out more difficult than expected. This package is not going to be published, so I'm really only interested in ESM.

I was able to do this as a plain js-package, but now that I'm converting it to TS, I'm running into problems.

My question is, how to import from an external package? I use various Lodash functions. But Rollup complains that they don't exist, and/or have to be exported as well.

I've included the first function that I was putting into this lib, I'm very new to TS, so don't mind that too much. ;-)

[!] RollupError: "now" is not exported by "../../node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/lodash.js", imported by "out-tsc/src/createUid.js".
https://rollupjs.org/troubleshooting/#error-name-is-not-exported-by-module
out-tsc/src/createUid.js (1:9)
1: import { now, random, padStart } from "lodash";
            ^

This my latests setup, going through many many variations:


Config

package.json

{
  "name": "@vexna/util",
  "version": "1.0.0",
  "description": "Generic utilities, uses lodash",
  "private": true,
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "sideEffects": false,
  "scripts": {
    "build": "rimraf dist && tsc && rollup -c rollup.config.js",
    "test": "node test/spec",
    "pretest": "npm run build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.20.12",
    "@babel/preset-env": "^7.20.2",
    "@open-wc/building-rollup": "^2.2.1",
    "@rollup/plugin-babel": "^6.0.3",
    "@rollup/plugin-node-resolve": "^15.0.1",
    "@types/lodash": "^4.14.191",
    "deepmerge": "^4.3.0",
    "lodash": "^4.17.21",
    "rimraf": "^4.1.2",
    "rollup": "^3.12.1",
    "typescript": "^4.9.5"
  },
  "peerDependencies": {
    "lodash": "^4.17.21"
  },
  "files": [
    "dist"
  ]
}

tsconfig.json

{
    "compilerOptions": {
      "target": "es2018",
      "module": "esnext",
      "moduleResolution": "node",
      "noEmitOnError": true,
      "lib": ["es2017"],
      "strict": true,  
      "esModuleInterop": false,
      "outDir": "out-tsc",
      "rootDir": "./",
      "skipLibCheck": true,
      "declaration": true,
      "allowSyntheticDefaultImports": true
    },
    "include": ["./src/**/*.ts"]
}

rollup.config.js

import merge from 'deepmerge';
import { createBasicConfig } from '@open-wc/building-rollup';

const baseConfig = createBasicConfig();
export default merge(baseConfig, {
    input: ['./out-tsc/src/index.js'],
    output: {
        format: "esm",
        exports: "named",
        dir: 'dist',
    },
    external: ['loadash'],
});

.babelrc

{
    "presets": [["@babel/env", { "modules": false }]]
}

Code

I've organised the code as follows:

/src
/src/index.ts
/src/createUid.ts

createUid is the first function that I was putting into this lib. I'd like to separate each function into its own file (but if they must all be in one file, that's fine too).

createUid.ts

import { now, random, padStart } from "lodash"

/**
 * Return a 16-digit unique integer based on the current time (ms) appended
 * with a three-digit random or provided number ("counter").
 * 
 * The id is an integer and consists of two parts:
 *   1) The number of miliseconds is a 13-digit number
 *   2) Appended with a three digit number, that is either:
 *      a) a left-padded number, if provided to the function
 *      b) a random numer
 * 
 * 1675246953915    February 1st, 2023 (today)
 * 9999999999999    November 20th, 2286
 * 9007199254740    June 5th, 2255
 * 9007199254740991 Max. safe integer
 *
 * Note:
 *  - This function won't work after November, 2286.
 *    If it is still in use then consider adding two instead of three digits,
 *    or use a bigint.
 * 
 */

const createUid = (counter?: number): (number|undefined) => {
    let p1 = now() // ms
    let p2 = ""
    
    if (counter == undefined) {
        p2 = padStart(random(0,999).toString(), 3, '0')
    } else if (isNaN(counter)) {
        p2 = padStart(random(0,999).toString(), 3, '0')
    } else {
        let carry = 0
        if (counter > 999) {
            counter = counter % 1000
            carry = Math.trunc(counter / 1000)
        }
        p2 = padStart(counter.toString(),3,'0')
        if (carry > 0) {
            p1 += carry
        }
    }

    // Create the integer
    const retVal = parseInt(`${p1}${p2}`)

    // Check if safe
    if (!Number.isSafeInteger(retVal)) {
        console.error(`Generated id is larger than safe integer ${Number.MAX_SAFE_INTEGER}.`)
        return
    }

    return retVal
}

export { createUid }

index.ts

export { createUid } from './createUid'

I found two three possible working scenarios:

  1. Use TSdx, the Zero-config CLI for TypeScript package development. https://tsdx.io/
  2. Use lodash-es instead (but note that chains are not working in lodash-es.). Explanation below, This however still gives Jest errors when this library is imported in another library. and tested there.
  3. Use vitest instead. Vitest is largely compatible with the Jest api. After installing Vitest, everything simply worked, without any configuration. The only thing to do is to add import { assert, expect, test } from 'vitest' to your tests. If you have just started, and only have simple tests, they simply worked without any changes (YMMV). In addition, both lodash and lodash-es worked fine.

A better answer would include a scenario that uses "plain" lodash, or explains why lodash-es is actually a good thing. From what I'm reading in various posts is that lodash-es has several drawbacks (results in a larger bundle size, chains don't work).

Here is a working setup with (I think) sensible defaults. This library is for internal use, and I run Node 18, so I use a high target version here.

package.json

{
  "name": "@vexna/util",
  "version": "1.0.0",
  "description": "Generic utilities, uses lodash",
  "private": true,
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "sideEffects": false,
  "scripts": {
    "build": "rimraf dist && tsc && rollup -c rollup.config.js",
    "test": "node test/spec",
    "pretest": "npm run build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@rollup/plugin-typescript": "^11.0.0",
    "@types/lodash-es": "^4.17.6",
    "lodash-es": "^4.17.21",
    "rimraf": "^4.1.2",
    "rollup": "^3.12.1",
    "typescript": "^4.9.5"
  },
  "files": [
    "dist"
  ],
  "peerDependencies": {
    "lodash": "^4.17.21"
  }
}

tsconfig.ts

{
    "compilerOptions": {
      "module": "es2022",
      "moduleResolution": "node",
      "outDir": "dist",
      "declaration": true,
      "sourceMap": true,
      "esModuleInterop": true,
      "allowSyntheticDefaultImports": true,
      "forceConsistentCasingInFileNames": true,
      "lib": ["es2022", "DOM", "DOM.Iterable"],
      "target": "es2022",
      "skipLibCheck": true,
      "strict": true,
      "exactOptionalPropertyTypes": true,
      "noImplicitAny": true,
      "noImplicitThis": true,
      "checkJs": true
    },
    "include": ["./src/**/*.ts"]
}

rollup.config.js

import typescript from '@rollup/plugin-typescript'
const input = ["src/index.ts"]

export default [
    {
        input,
        plugins: [
            typescript()],
        output: [
            {
                dir: "dist",
                sourcemap: true,
            }
        ],
        external: ['lodash-es'],
    }
]

The output has lodash entirely externalized, so very small bundle, As a precaution: I added "lodash". "^4.17.21" to the "peerDependencies", This is a little odd cause it's a mismatch with devDependencies (which uses the -es). but this seems fine. YMMV.

In the library code, you can import the lodash functions like so:

import { now, random, padStart } from "lodash-es"

A project that consumes this library must have lodash, and can import like so:

import { createUid } from '@vexna/util'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM