Skip to content

Instantly share code, notes, and snippets.

@azu
Last active January 16, 2023 14:46
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azu/f383ba74c80d17806badd49745ce2129 to your computer and use it in GitHub Desktop.
Save azu/f383ba74c80d17806badd49745ce2129 to your computer and use it in GitHub Desktop.
Migration Script: Convert TypeScript project to Node.js dual package

Convert TypeScript library project to Node.js Dual Package

It is a script to convert a TypeScript library project to a Node.js Dual CommonJS/ES module packages.

This script aim to convert following project:

  • Use TypeScript
  • Use ts-node
  • Use mocha

This script modify following files:

  • package.json: Add exports field and Install tsconfig-to-dual-package to build dual package
  • .mocharc.json: Support ESM via ts-node/esm
  • tsconfig.json: Compile source code to ESM
  • tsconfig.cjs.json: Compile source code to CJS
  • .gitignore: Ignore compiled files

As a result, your library will be following

  • Your Library has { "type" : "module" } and exports field
  • Your Library can be used from CJS(require) and ESM(import)

Requirement

  • Node.js 16+
  • npm 8+ (npm pkg command is used)

Usage

Install dependencies to project and update package.json:

$ bash <(curl -s https://gist.githubusercontent.com/azu/f383ba74c80d17806badd49745ce2129/raw/convert-to-dual-package.sh)

Check source code via eslint-cjs-to-esm

$ npx eslint-cjs-to-esm "./{src,test}/**/*.{js,ts}"

Convert sourc code to ESM via eslint-cjs-to-esm

$ npx eslint-cjs-to-esm "./{src,test}/**/*.{js,ts}" --fix

Test project via publint

npm test
npm run clean && npm run build && npx publint

Examples

References

License

MIT © azu

#!/bin/bash
#
# Convert TypeScript project to Node.js dual package
#
# Require
# - Node.js 16+
# - npm 8+
declare scriptDir=$(cd $(dirname ${BASH_SOURCE:-$0}); pwd)
declare currentDir=$(pwd)
declare currentDirName=$(basename "${currentDir}")
# lib
function installDev() {
local isNpm=$(test -e "${currentDir}/package-lock.json" && echo "true" || echo "false")
if [[ "$isNpm" == "true" ]]; then
npm install -D "$@"
else
yarn add --dev "$@"
fi
}
function uninstall() {
local isNpm=$(test -e "${currentDir}/package-lock.json" && echo "true" || echo "false")
# iterate $@ and delete each package from dependencies/devDependencies/peerDependencies
for depName in "$@"; do
npm pkg delete "dependencies.$depName"
npm pkg delete "devDependencies.$depName"
npm pkg delete "peerDependencies.$depName"
done
if [[ "$isNpm" == "true" ]]; then
npm install
else
yarn install
fi
}
function echo_message(){
echo "🤖 $1"
}
# Install
echo_message "npm install"
# "main": "./lib/index.js" -> "index"
declare entryFileName=$(npm pkg get main | xargs -I{} basename {} .js)
installDev typescript ts-node ts-node-test-register mocha @types/node @types/mocha tsconfig-to-dual-package
# uninstall old pakacge
# ts-node-test-register cross-env
# Copy config
echo_message "Copy .tsconfig mocha"
# remote
curl https://gist.githubusercontent.com/azu/f383ba74c80d17806badd49745ce2129/raw/tsconfig.json -o "${currentDir}/tsconfig.json"
curl https://gist.githubusercontent.com/azu/f383ba74c80d17806badd49745ce2129/raw/tsconfig.cjs.json -o "${currentDir}/tsconfig.cjs.json"
curl https://gist.githubusercontent.com/azu/f383ba74c80d17806badd49745ce2129/raw/mocharc.json -o "${currentDir}/.mocharc.json"
# local
# cp "${scriptDir}/sconfig.json" ./
# cp "${scriptDir}/tsconfig.cjs.json" ./
# cp "${scriptDir}/.mocharc.json" ./
# Edit package.json
## Add script
echo_message "Add npm run-script"
npm pkg set scripts.build="tsc -p . && tsc -p ./tsconfig.cjs.json && tsconfig-to-dual-package"
npm pkg set scripts.watch="tsc -p . --watch"
npm pkg set scripts.clean="git clean -fx lib/ module/"
npm pkg set scripts.prepublishOnly="npm run clean && npm run build"
npm pkg set scripts.test="mocha"
echo_message "Add package.json fields"
## update dual package
npm pkg set "type"="module"
npm pkg set "main"="./lib/${entryFileName}.js"
npm pkg set "types"="./module/${entryFileName}.d.ts"
npm pkg set "module"="./module/${entryFileName}.js"
npm pkg set "exports[.].import.types"="./module/${entryFileName}.d.ts"
npm pkg set "exports[.].import.default"="./module/${entryFileName}.js"
npm pkg set "exports[.].require.types"="./lib/${entryFileName}.d.ts"
npm pkg set "exports[.].require.default"="./lib/${entryFileName}.js"
npm pkg set "exports[.].default"="./lib/${entryFileName}.js"
npm pkg set "exports[./package.json]"="./package.json"
# update "files"
npm pkg delete files
npm pkg set files[]='./lib/' files[]='./bin/' files[]='./module/' files[]='./src/'
# "exports": {
# ".": {
# "types": "./module/{entry}.d.ts",
# "import": "./module/{entry}.js",
# "require": "./lib/{entry}.js",
# "default": "./module/{entry}.js"
# },
# "./package.json": "./package.json"
# },
echo_message "Add .gitignore"
echo "/module/" >> ".gitignore"
echo "/lib/" >> ".gitignore"
npx @turbo/codemod add-package-manager --force
sort-package-json
# git
git add .
{
"$schema": "https://json.schemastore.org/mocharc",
"loader": "ts-node/esm",
"spec": [
"test/**/*.ts"
]
}
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"outDir": "./lib/",
}
}
{
"compilerOptions": {
/* Basic Options */
"module": "ESNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"newLine": "LF",
"outDir": "./module/",
"target": "ES2018",
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"jsx": "preserve",
"lib": [
"esnext",
"dom"
],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src/**/*"
],
"exclude": [
".git",
"node_modules"
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment