DEV Community

Cover image for Create ESM monorepo project
Minsup Ju
Minsup Ju

Posted on

Create ESM monorepo project

Photo by Leone Venter on Unsplash



Learn once, write anyware. - React Native


It is welcome comment from React Native. Nowaday, it is easy to do so thanks to advances of web technology. So I try to do it myself.

ESM

There are many way to import and export other file in JavaScript, such as CommonJS, AMD, ESM, etc. I'm going to set up my project with ESM.

ESM(ECMA*Script **M*odule) is module feature for ECMA script from ES6(ES2015). Import and export would looks like-

// greeter.js
function greeter() {
  console.log('Hello ESM');
}

export default greeter;
Enter fullscreen mode Exit fullscreen mode
// index.js
import greeter from './greeter.js'

greeter();
Enter fullscreen mode Exit fullscreen mode

ESM is supported by most web browsers, except IE and some mobile browser.
In Node.js, it becames stabilized from v12.22.0.

It is possible for ESM to import CommonJS module, CommonJS module cannot import ESM.

I think there are more and more npm packages which is pure ESM(node-fetch, chalk, etc.), so I decide to develop ESM package.

Monorepo

A monorepo(mono + repository) is a repository that contains multiple packages, such as Babel or Webpack CLI.

There are many tools for manage monorepo, like Lerna. Package managers like Yarn and pnpm support monorepo through workspace feature. Also, npm supports workspace feature from v7.

This time I'll use Yarn v2.

Initialize project

First, create Yarn v2 project.

yarn init -2
Enter fullscreen mode Exit fullscreen mode

Add yarn plugins.

# "yarn upgrade-interactive" for interactive package upgrade.
yarn plugin import interactive-tools

# Automatically adds @types/* package if added package doesn't include its own types.
yarn plugin import typescript

# Add some command for manage workspace, like "yarn workspaces foreach".
yarn plugin import workspace-tools
Enter fullscreen mode Exit fullscreen mode

Edit .yarnrc.yml to turn off Plug'n'Play. I prepare to use old node_modules as there are some packages still have problem with P'n'P, and other packages use yarn v2 also use node_modules.

# .yarnrc.yml
nodeLinker: node-modules

plugins:
  - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
    spec: '@yarnpkg/plugin-interactive-tools'
  - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
    spec: '@yarnpkg/plugin-typescript'
  - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
    spec: '@yarnpkg/plugin-workspace-tools'

yarnPath: .yarn/releases/yarn-3.1.1.cjs
Enter fullscreen mode Exit fullscreen mode

Edit .gitignore according to turning off Plug'n'Play.

# Yarn v2
/.yarn/*
!/.yarn/patches
!/.yarn/plugins
!/.yarn/releases
!/.yarn/sdks
/.pnp.*

# Dependency directories
node_modules/

...
Enter fullscreen mode Exit fullscreen mode

Add .gitattributes to exclude yarn v2 binary from git diff.

/.yarn/releases/** binary
/.yarn/plugins/** binary
Enter fullscreen mode Exit fullscreen mode

Add common dependencies for packages in monorepo.

# ESLint and Prettier
yarn add -D eslint eslint-config-prettier prettier

# TypeScript and ESLint plugins
yarn add -D typesciprt @typescript-eslint/eslint-plugin @typescript-eslint/parser

# Git hook for lint
yarn add -D husky
yarn dlx mrm lint-staged
Enter fullscreen mode Exit fullscreen mode

Finally create and edit .editorconfig, README.md, etc.

Configure project

Add configuration to package.json.

Set engine to use ESM.

// package.json
{
    ...

    "engines": {
        "node": "^14.13.1 || >=16.0.0"
    },

    ...
}
Enter fullscreen mode Exit fullscreen mode

Since it is root repository of monorepo, I don't add "type": "module".

Use packages folder for shared libraries for monorepo, and apps folder for applications of monorepo.

// package.json
{
    ...

    "workspaces": [
        "apps/*",
        "packages/*"
    ],

    ...
}
Enter fullscreen mode Exit fullscreen mode

Add project to parser config of ESLint.

// package.json
{
    ...

    "eslintConfig": {
        ...

        "parserOptions": {
            "project": [
                "./apps/**/tsconfig.json",
                "./packages/**/tsconfig.json"
            ]
        },

        ...
    },

    ...
}
Enter fullscreen mode Exit fullscreen mode

Finally package.json looks like-

// package.json
{
    ...

    "engines": {
        "node": "^14.13.1 || >=16.0.0"
    },
    "packageManager": "yarn@3.1.1",
    "workspaces": [
        "apps/*",
        "packages/*"
    ],
    "scripts": {
        "prepare": "husky install",
        "build": "yarn workspaces foreach run build",
        "test": "yarn workspaces foreach run test",
        "test:coverage": "yarn workspaces foreach run test:coverage"
    },
    "devDependencies": {
        "@typescript-eslint/eslint-plugin": "^5.9.1",
        "@typescript-eslint/parser": "^5.9.1",
        "eslint": "^8.6.0",
        "eslint-config-prettier": "^8.3.0",
        "husky": "^7.0.4",
        "prettier": "^2.5.1",
        "typescript": "^4.5.4"
    },
    "eslintConfig": {
        "root": true,
        "extends": [
            "eslint:recommended",
            "plugin:@typescript-eslint/recommended",
            "prettier"
        ],
        "parser": "@typescript-eslint/parser",
        "parserOptions": {
            "project": [
                "./apps/**/tsconfig.json",
                "./packages/**/tsconfig.json"
            ]
        },
        "plugins": [
            "@typescript-eslint"
        ]
    },
    "prettier": {
        "printWidth": 120,
        "singleQuote": true,
        "trailingComma": "all"
    },
    "lint-staged": {
        "*.{ts,tsx}": "eslint --cache --fix",
        "*.{ts,tsx,yml,md}": "prettier --write"
    }
}
Enter fullscreen mode Exit fullscreen mode

Add tsconfig.json since I'll use TypeScript.

{
    "compilerOptions": {
        "target": "ESNext",
        "lib": [
            "ESNext"
        ],
        "module": "ESNext",
        "moduleResolution": "Node",
        "resolveJsonModule": false,
        "allowJs": true,
        "noEmit": true,
        "importHelpers": true,
        "isolatedModules": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "noFallthroughCasesInSwitch": true,
        "skipLibCheck": true
    },
    "exclude": [
        "node_modules"
    ]
}
Enter fullscreen mode Exit fullscreen mode
  • In case of "module", I should be ES2020 or ESNext. If it is set to ES2015 or ES6, dynamic import and import.meta won't be supported.
  • "resolveJsonModule" is not supported by ESM yet.

Now I finished configuration of monorepo's root repository.

Summary

It took quite long to configure project. Next time I'll find some interesting subject and start to develop module.

ESM confiugration reference - Pure ESM package | GitHub Gist

Top comments (0)