What is the best way to bundle a TypeScript package along with its dependencies for seamless integration with various Next.js projects on a local environment

Currently, I am immersed in a project with the following arrangement:

/         # repository root
/core     # A local, unpublished npm package used by both projectA and projectB
/projectA # A Next.js app
/projectB # Another Next.js app

In my setup, I generate a core-1.0.0.tgz file by running tsc && npm pack on /core/package.json and /core/tsconfig.json. The transpilation process seems to run smoothly without any errors, resulting in the expected transpiled .js code being present in the .tgz file.

/core/tsconfig.json

{
  "compilerOptions": {
    "lib": ["es2022", "DOM"],
    "module": "commonjs",
    "target": "es2022",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": ".compiled",
    "declaration": true,
    "jsx": "react"
  },
  "include": ["./index.ts"]
}

/core/package.json

{
  "name": "core",
  "version": "1.0.0",
  "engines": {
    "node": "18.15.0"
  },
  "main": "./.compiled/index.js",
  "types": "./.compiled/index.d.ts",
  "files":[
    "./.compiled/**"
  ],
  "scripts": {
    "test": "ts-node ./tests.ts",
    "build": "npm install && npm run test && tsc && npm pack"
  },
  "dependencies": {
    ...
    "@uppy/aws-s3": "3.3.1",
    ...
  }
  ...
}

To integrate the core package into projectA, I modify /projectA/package.json as follows:

...
"dependencies": {
  ...
  "core": "../core/core-1.0.0.tgz",
  ...
},
...

After executing npm install in /projectA, everything appears functional. Upon inspection of projectA/node_modules, not only do I see the transpiled JS files under the core folder, but also the dependencies from core have been installed into projectA/node_modules.

However, upon launching projectA, an error surfaces immediately related to a core import, stating:

Error: require() of ES Module C:\dev\repo\projectA\node_modules\@uppy\aws-s3\lib\index.js from C:\dev\repo\projectA\node_modules\core\.compiled\library\components\file-uploader\FileUploader.js not supported.
Instead change the require of index.js in C:\dev\repo\projectA\node_modules\core\.compiled\library\components\file-uploader\FileUploader.js to a dynamic import() which is available in all CommonJS modules.

@uppy/aws-s3 stands as one of the sub-dependencies of core. The error emanates from FileUploader.js, a transpiled version of FileUploader.ts React component designed as a UI wrapper for @uppy. Although understanding the error message, I feel slightly disoriented amidst the layers. Presumably, tweaking /core/tsconfig.json is necessary, yet pinpointing precisely what requires alteration remains elusive.

/core/.../FileUploader.ts

import UppyAwsS3 from '@uppy/aws-s3'
import Uppy, { SuccessResponse, UppyFile } from '@uppy/core'
import UppyDashboard from '@uppy/dashboard'
import cx from 'classnames'
import React from 'react'

interface Props {
  className?: string
  fullWidth: boolean
  ...
}

interface State {}

class FileUploader extends React.Component<Props, State> {
  ...
  render = () => {
    return (
      <div className={cx('FileUploader', this.props.className, this.props.fullWidth && 'FileUploader--fullWidth')}>
        ...
      </div>
    )
  }
}

export default FileUploader

Provided here is /projectA/tsconfig.json for reference:

{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "jsx": "preserve",
    "lib": ["ES2022", "dom"],
  },
  "exclude": [
    "node_modules"
  ]
}

If anyone holds insights on establishing this shared TypeScript package, or notices discrepancies in my current setup, your assistance would be highly appreciated.

Edit: Advice has been given to refer to this question. However, my scenario involves TypeScript rather than plain JavaScript. My inquiry pertains to configuring tsc to compile the correct import syntax, allowing Next.js to utilize the shared FileUploader component from my core CommonJS package dependent on the ESM package @uppy/aws-s3.

Update: To aid in troubleshooting, I've prepared a basic sample project illustrating the error. It can be accessed here.

Answer №1

If you find yourself in need of a tool for managing and publishing multiple JavaScript/TypeScript packages within the same repository, consider using Lerna.

Lerna is a cutting-edge build system that offers speed and efficiency in handling various projects simultaneously.

In essence, Lerna specializes in overseeing multi-project packages.

By running 'lerna init', you can create a packages directory where your packages will reside. You can establish links between these packages through:
'lerna add shared --scope=packageA'

This command allows you to incorporate the 'shared' module into the 'packageA' node modules. By adding this dependency, you refer to the 'name' specified in the 'package.json' file of the shared module.

// Inside the package.json file of packageA:
  "dependencies": {
    "nameOfPackageJsonOfSharedModule": "^1.0.0",
  }

Upon completing these steps, you are now able to utilize the 'shared' module within 'packageA'.

// The 'nameOfPackageJsonOfSharedModule' represents the previously mentioned dependency
const share = require("nameOfPackageJsonOfSharedModule")

If integrating the module does not yield the desired outcome, consider building each package separately and referencing the build directory when importing modules. For example, if you have built the 'shared' module, include the following line in its package.json file:

// Upon importing this package, 'build/index.js' will be executed
"bin": "build/index.js"

Answer №2

To properly configure your project, make sure to include

"module": "commonjs"
in the compilerOptions. Depending on your specific setup, you may need to adjust other settings as well.

Alternatively, I recommend utilizing the TurboRepo setup, which is officially endorsed by Next.js. For a helpful reference, take a look at this template repository.

Check out some of the packages I have developed using this configuration:

Feel free to explore more possibilities!

Answer №3

Forget about npm and switch to pnpm instead! By packaging your dependencies, pnpm allows you to easily use them in multiple projects.

Visit pnpm's website for more information.

Answer №4

After a thorough investigation and various attempts, I successfully managed to compile and link my example without the need to install any extra packages or dependencies. It was truly a journey of trial and error, but I finally got everything up and running smoothly.

Here are the key changes that made it possible:

  • core/tsconfig.json
    • Following Icekid's recommendation, I switched compilerOptions.module and compilerOptions.target to ES6.
    • Included
      compilerOptions.moduleResolution: "Node"
      ; this step was crucial as TypeScript was not recognizing my import statements during the tsc run without it.
  • projectA/next.config.js
    • Added transpilePackages: ['core']. As highlighted in this discussion on Next.js quirks, I discovered that Next.js doesn't automatically transpile locally imported ES6 modules per the tsconfig.json. This required manual intervention for my custom local package 'core', unlike other packages like uppy/aws-s3 which were auto-transpiled without issues within projectA.

With these adjustments in place, everything fell into line seamlessly while all other settings remained unchanged.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

TS - Custom API hook for making multiple API requests - incompatible type with 'IUseApiHook'

What is my objective? I aim to develop a versatile function capable of handling any type of API request for a frontend application. Essentially, I want to add some flair. Issue at hand? I find myself overwhelmed and in need of a fresh perspective to revi ...

The EventEmitter in Angular 8 is prohibiting the emission of an Object

Is there a way I can emit an Object instead of primitive data types? I have an object const amount = { currenty: 'USD', total: '10.25' }; that I need to emit to the parent component. export class MyChildComponent implements OnInit { ...

React 18 Fragment expressing concern about an excessive amount of offspring

Recently, I attempted to integrate Storybook into my React application, and it caused a major disruption. Despite restoring from a backup, the absence of the node_modules folder led to issues when trying 'npm install' to recover. Currently, Types ...

Using Node.js to stream an MJPEG feed to a client-side application

I am currently working on integrating a live stream from an Axis camera into my Next.js front-end. I have set up a custom Node.js server that interacts with the Axis API to fetch the video stream data using the following code snippet: const response = awai ...

Set up a global variable for debugging

Looking to include and utilize the function below for debugging purposes: export function debug(string) { if(debugMode) { console.log(`DEBUG: ${string}`) } } However, I am unsure how to create a globally accessible variable like debugMode. Can this be ...

Looking to retrieve the request body in a route handler in Next.js version 13.2?

I encountered an issue while attempting to send a post request to my API. The problem arises when I try to access the request body within the route handler, resulting in the following error: Code: export async function POST(request: Request) { const ...

Can you explain the significance of the @ symbol in TypeScript/Vue?

I've recently embarked on a new VueJS project and stumbled upon some unfamiliar syntax. I have highlighted the lines with comments below. file(example) : MyModule.vue const storeCompte = namespace("compte") // namespace is based on 'no ...

An issue occurred with npm resulting in exit code 2. The error is as follows: Command was unsuccessful: node_modules/.bin/vsce package --no

Here is the specific error displayed in cmd: TS1323: Dynamic imports are only supported when the '--module' flag is set to 'es2020', 'esnext', 'commonjs', 'amd', 'system', or 'umd'. E ...

Determine the scroll location using NextJS

I'm trying to determine if the user has scrolled in order to update the UI using NextJS. I have come across various examples with similar code, such as this one: const [scrollY, setScrollY] = useState(0); const onScroll = (event) => { cons ...

Importing configuration file in CRA Typescript with values for post-deployment modifications

I am currently working on a React app that utilizes Create React App and Typescript. My goal is to read in configuration values, such as API URLs. I have a config.json file containing this data, here's a sample snippet with placeholder information: { ...

The functionality of nested dynamic routing in the API is experiencing issues

Looking to extract product details from a specific category of products? My folder structure appears as follows: https://i.stack.imgur.com/1UCy3.png In "productId/index.jsx" => This snippet is utilized to retrieve individual product details: ...

The validation through class-validator or class-transformer fails to function properly when applying multiple Types to the @Query decorator

Is there a way to combine multiple types with the @Query() decorator in my controller, such as ParamsWithRegex and PaginationParams? I'm facing an issue where no validation is being applied when I do that. How can I resolve this problem? **// MY CON ...

Issues detected in the performance of custom Express Server on NextJs

Switching to NextJs has been a challenge for me. I have developed an ExpressJs API and now I want to integrate it as a Custom NextJs Server, following the documentation. Unfortunately, I can't seem to get it working properly. Below is my server.ts fi ...

Encountering an issue while trying to import the instanceMethods function within Sequelize

In a file, I have written a function and exported it like this: export function foo(params...) { // do something } When initializing the model, I imported the function in the following manner: import { foo } from "../path/..." instanceMethods: { foo ...

Removing an image from the files array in Angular 4: A step-by-step guide

I have recently started working with typescript and I am facing a challenge. I need to remove a specific image from the selected image files array before sending it to an API. app.component.html <div class="row"> <div class="col-sm-4" *ngFor ...

Incorporating HTTP status codes into error handling

I have developed an API where I've organized the services separately from the controllers. In my service functions, I've included basic checks to trigger errors when certain conditions are met. Currently, my controller function just returns a 500 ...

Converting a string to a Date using TypeScript

Is it possible to convert the string 20200408 to a Date using TypeScript? If so, what is the process for doing so? ...

The value returned by a mocked Jest function is ignored, while the implemented function is not invoked

Having an issue with mocking the getToken function within my fetchData method in handler.ts while working with ts-jest. I specifically want to mock the response from getToken to avoid making the axios request when testing the fetchData method. However, des ...

Having trouble viewing the initial value in an AngularJS2 inputText field

I'm having trouble displaying the initial value in inputText. I'm unsure of what mistake I'm making, but the value should be showing up as expected. Kind regards, Alper <input type="text" value="1" [(ngModel)]="Input.VSAT_input1" name= ...

Conceal components using routing in Angular2 release candidate 1

I have a request regarding certain elements that are to be displayed on all pages except the login page. I am considering using either ngIf or the hidden property of the elements to hide them when the user is on the login page. Here is what I have attempt ...