Giter Site home page Giter Site logo

tsc-esm's Introduction

You should not use this package!

Since TS 4.7 it is now possible to set the moduleResolution field to node16 or nodeNext which enforces the use of the .js extension at the end of the imports.

Previously, Typescript allowed to not add the imported file extension if it was a Typescript or Javascript file. This behavior was opposed to the recent developments of Ecmascript. Browsers, Node and Deno all needed the .js extension.

I first developped tsc-esm as a patch to the tsc compiler that would add the .js extension. But this technique has drawbacks. For example, it cannot deal with Typescript aliases. I strongly encourage to follow the Ecmascript guidelines and the recent Typescript extension, that is to use Typescript@^4.7 and to set the moduleResolution field to node16 or nodeNext in tsconfig.json.

tsc-esm is a small wrapper library can be used to replace Typescript's tsc compilation command when generating modern Javascript with ES6 modules.

The problem

In order to use ES6 modules either in a browser, a Node (with "type": "module") or a Deno environment, a ".js" extension is mandatory at the end of each import statement.

For example, you can do:

import foo from './foo.js'

but you cannot do:

import foo from './foo'

However Typescript compiler do understand the latter case and it's even recommended to use it. Some linters will complain if you add the unnecessary ".js" or ".ts" extension.

In the same way, Typescript can understant when you a /foo/index.ts directory structure, you can do

import foo from '/foo'

and Typescript will understand you need to import the index.ts file.

That's actually great... Until you want to compile your Typescript code to modern Javascript with ES6 modules. As it is explained well enough in this long issue: provide a way to add the '.js' file extension to the end of module specifiers, the Typescript team considers that since users can manually add themselves a ".js" extension to all their imports, this is not an issue.

In my opinion it is still an issue because:

  1. valid Typescript code can be compiled with no errors and still generate invalid Javascript (and all platforms disagree it is invalid: Browser, Node and Deno),
  2. even if it works, it is semantically incorrect to write import foo from "/foo/index.js" when your directory structure is /foo/index.ts, because you are explicitly importing to a file that does not exist yet (it will exist after compilation).

The solution

Use tsc-esm instead of tsc.

tsc-esm works in two simple steps:

  1. it calls tsc,
  2. it uses the grubber library to safely parse the generated javascript files and patch the import expressions.

It is highly recommended that you have a tsconfig.json configuration file in your root project with either compilerOptions.outDir or include option set ; otherwise all .js files in your project will be scanned and transformed.

CLI

Global installation
npm i -g @digitak/tsc-esm
tsc-esm
Local installation
npm i -g @digitak/tsc-esm

Then add a script in your package.json:

{
   "scripts": {
      "build": "tsc-esm"
   }
}

Then you can run:

npm run build

API

import { build } from '@digitak/tsc-esm'

build()

Or:

import { compile, patch } from '@digitak/tsc-esm'

compile()
patch()
Aliases

You can pass aliases to the build or the patch functions to control how some paths should be transformed or not:

function build(aliases?: Array<AliasResolver>): void
function patch(aliases?: Array<AliasResolver>): void

type AliasResolver = {
   find: RegExp; // the path to match
   replacement: string | null; // the replacement value
};

The function String::replace is used internally so you can replace your path using special patterns like $1or $&.

If the replacement value is null, the path will be left untransformed.

It can be useful when working when libraries that don't have clean type definitions.

If you have a chokidar dependency for example you might need to tell tsc-esc to not patch this specific import:

build([
   { find: /^chokidar$/, replacement: null },
])

Then all import chokidar from 'chokidar' statements will be left unchanged, otherwise it would have been transformed into import chokidar from 'chokidar/index.js' which is not typed.

tsc-esm's People

Contributors

diefbell avatar gin-quin avatar jrkienle avatar lemaik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

tsc-esm's Issues

Local installation is broken

Hi

I installed this lib locally (yarn add @digitak/tsc-esm --dev but I could do npm i @digitak/tsc-esm for the same result), as a result I got:

node_modules/bin/tsc-esm file with contents:

#!/usr/bin/env node
import { build } from "../library/index.js"
build().catch(error => {
	console.error(error)
	process.exit(1)
})

which of course breaks, because I don't have node_modules/library/index.js file installed.

Also README seems to be wrong and with both Global installation and Local installation it suggests installing the lib with -g flag, which of course means global.

ERR_PACKAGE_PATH_NOT_EXPORTED on fastify-openapi-glue

First of all, i'm sorry if this might be an external issue, but everything is working fine on tsc.

Does anyone has ever stumble to this issue?

[tsc-esm] Could not build: Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: No "exports" main defined in node_modules\fastify-openapi-glue\package.json
    at new NodeError (node:internal/errors:371:5)
    at throwExportsNotFound (node:internal/modules/esm/resolve:440:9)
    at packageExportsResolve (node:internal/modules/esm/resolve:645:7)
    at resolveExports (node:internal/modules/cjs/loader:482:36)
    at Function.Module._findPath (node:internal/modules/cjs/loader:522:31)
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:919:27)
    at Function.resolve (node:internal/modules/cjs/helpers:108:19)
    at resolve (file:///C:/repo/github/place-server/node_modules/@digitak/grubber/utilities/patchJsImports.js:8:6)
    at file:///C:/repo/github/place-server/node_modules/@digitak/grubber/utilities/patchJsImports.js:38:32
    at addJsExtensions (file:///node_modules/@digitak/grubber/utilities/addJsExtensions.js:12:31) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

I've added "type": "module" in package.json and updated my tsconfig.json to ES Module.
If I use tsc, it builds successfully, but not on tsc-esm.
Thanks

TypeError: Cannot read properties of undefined (reading 'catch')

Hello! I recently installed this package and ran into an error on first use. I followed the documentation to the best of my knowledge, and am a little stuck. Here's the full error stack:

file:///workspaces/monorepo-tools/node_modules/.pnpm/@[email protected]/node_modules/@digitak/tsc-esm/binary/tsc-esm.js:3
build().catch(error => {

TypeError: Cannot read properties of undefined (reading 'catch')
    at file:///workspaces/monorepo-tools/node_modules/.pnpm/@[email protected]/node_modules/@digitak/tsc-esm/binary/tsc-esm.js:3:8
    at ModuleJob.run (node:internal/modules/esm/module_job:185:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:281:24)
    at async loadESM (node:internal/process/esm_loader:88:5)
    at async handleMainPromise (node:internal/modules/run_main:65:12)
 ELIFECYCLE  Command failed with exit code 1.
 ```
 
 Here's my TSConfig

 ```
 {
  "exclude": ["node_modules"],
  "extends": "@kienleholdings/base-tsconfig",
  "include": ["./src"],
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
    "module": "es2020",
    "outDir": "lib",
    "target": "es2021",
    "types": ["node"]
  }
}

And here's the relevant portion of my package.json

{
  ...
  "type": "module",
  "scripts": {
    "build": "tsc-esm"
  },
  "devDependencies": {
    "@digitak/tsc-esm": "^3.1.1",
    "typescript": "^4.5.5"
  }
  ...
}

Let me know if there's any other information you need! I love this solution to the esm importing problem, and hopefully one day it'll simply be a part of TypeScript

Readme notice?

Hi,

I'm trying to understand the notice in the readme, but when I do this I have a bunch of errors like this one:

 error TS2835: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../models/OffsetParameter.js'?

I have actually made a similar plugin for esbuild and found this repo while trying ti decipher all this.
So I'm not sure if this is still an issue or not and if not, I'm not sure of the approach?

Thanks

tsc-esm fails on import something from '../directory/'

I have imports from directories like

import something from '../directory/';

where the directory ../directory/ contains a package.json with separate Node and browser entry points.

tsc is happy with this, but tsc-esm gives an error like

[tsc-esm] Could not build: Error: Cannot find module '../directory/'
Require stack:
- /Users/schneck/foo
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
    at Function.resolve (node:internal/modules/cjs/helpers:108:19)
    at resolve (file:///Users/schneck/foo/node_modules/@digitak/grubber/library/utilities/patchJsImports.js:7:52)
    at file:///Users/schneck/foo/node_modules/@digitak/grubber/library/utilities/patchJsImports.js:33:32
    at addJsExtensions (file:///Users/schneck/foo/node_modules/@digitak/grubber/library/utilities/addJsExtensions.js:12:31)
    at patchJsImports (file:///Users/schneck/foo/node_modules/@digitak/grubber/library/utilities/patchJsImports.js:29:40)
    at patchJsImports (file:///Users/schneck/foo/node_modules/@digitak/grubber/library/utilities/patchJsImports.js:22:17)
    at patch (file:///Users/schneck/foo/node_modules/@digitak/tsc-esm/library/index.js:27:2)
    at build (file:///Users/schneck/foo/node_modules/@digitak/tsc-esm/library/index.js:15:3)
    at file:///Users/schneck/foo/node_modules/@digitak/tsc-esm/binary/tsc-esm.js:4:2 {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/Users/schneck/foo' ]
}

I've tried with and without the trailing slash on ../directory/.

Non-JS imports cause errors

We're using this tool for a library that is supposed to be bundled by the consumer (we're using ESM for tree-shaking) and thus can import png files etc.

While @digitak/grubber/library/utilities/patchJsImports.js checks to only edit mjs, js, and cjs files, it doesn't check which imports to update but just tries to resolves anything with require.resolve, resulting in this error:

[tsc-esm] Could not build: Error: Cannot find module '../something.png'
Require stack:
- /home/.../my-project
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
    at Function.resolve (node:internal/modules/cjs/helpers:108:19)
    at resolve (file:///home/.../tsc-esm/node_modules/@digitak/grubber/library/utilities/patchJsImports.js:7:52)

One solution is to add a check to only change imports without file extensions. I'm not sure where this should be changed (upstream in @digitak/grubber or by copying patchJsImports into this project and patching it)?

Edit: As a workaround, we can require the images for now instead of importing them for now.

Importing files without default export

I have ts file with following imports (I just need to import and run that .ts script, it has no exports)

file.ts

import '../setup1'
import '../setup2'

It results in
file.js

import undefined from '../setup1.js';
import undefined from '../setup2.js';

which is obviously wrong and running this will fail as we are having import with the same name, it should result into this

file.js

import '../setup1.js'
import '../setup2.js'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.