Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use with local ambient declarations #10

Closed
shawnmcknight opened this issue Feb 6, 2019 · 10 comments
Closed

How to use with local ambient declarations #10

shawnmcknight opened this issue Feb 6, 2019 · 10 comments

Comments

@shawnmcknight
Copy link

I'm having a problem using ts-transform-paths alongside localized ambient declarations that are accessible through the paths configuration.

I'm utilizing an npm module that does not have its own .d.ts file and there is nothing in DefinitelyTyped @types for it either. In this case, I've created my own ambient declaration .d.ts file to substitute for it. In my project, I locate these declarations in ./src/types. Ordinarily typescript discovers these using a paths and baseUrl configuration of:

    "baseUrl": ".",
    "paths": {
      "*": [
        "./src/types/*",
        "./node_modules/*"
      ]
    }

I'm now looking to add aliases and consume them through ts-transform-paths. I've adjusted my paths and baseUrl configuration to be:

    "baseUrl": "./src",
    "paths": {
      "#mixins/*": [
        "./mixins/*"
      ],
      "#shared/*": [
        "./shared/*"
      ],
      "*": [
        "./types/*",
        "../node_modules/*"
      ]
    }

References to the aliases work perfectly after being processed by ts-transform-paths. However, non-aliased imports are being converted. For example, this import:

import { createJwt } from './createJwt';

has been transformed to:

const createJwt_1 = require("../../../../../types/createJwt");

If I eliminate the * property from the paths configuration, the transforming becomes correct:

const createJwt_1 = require("./createJwt");

The only way I've found to continue to have my ambient declarations get resolved is to relocate them in a parent folder so that node's module resolution can find it rather than using typescript's paths, but I'd prefer to not have to locate these declarations directly in my src folder or in the project's root folder.
Is there a manner in which I can configure my tsconfig.json to work for both ts-transform-paths and for discovering the ambient declarations?

Thanks!

@shawnmcknight
Copy link
Author

My thought process is that a consumer of ts-transform-paths would not want to transform a path that had a key of * and that would be reserved solely for the use of resolving ambient declarations. If that is the case then ts-helpers/ImportPathsResolver can have class ImportPathsResolver changed as follows:

        this.tokenizers = Object.keys(paths).map(key => new Tokenizer(
            key,
            mapBaseUrl ? paths[key].map(mapBaseUrl) : paths[key]
        ))
    }

becomes:

        this.tokenizers = Object.keys(paths).filter(key => key !== '*').map(key => new Tokenizer(
            key,
            mapBaseUrl ? paths[key].map(mapBaseUrl) : paths[key]
        ))
    }

That would eliminate the transformation of any paths with a key that is only an asterisk.

@zerkalica
Copy link
Owner

@shawnmcknight Probably fixed. Try version 1.6.6 of ts-transform-paths + ts-helpers

@shawnmcknight
Copy link
Author

Okay - I bumped my version of both and had some mixed results.

When running through ttsc to only compile, it appears that my relative paths beginning with ./ were no longer being transformed -- GOOD!

However, when running through ts-node (using the ttypescript compiler) I had different results. It was still transforming my paths beginning with ./. On a whim, I changed my paths configuration to this:

    "baseUrl": "./src",
    "paths": {
      "#mixins/*": [
        "mixins/*"
      ],
      "#shared/*": [
        "shared/*"
      ],
      "*": [
        "types/*",
        "../node_modules/*"
      ]
    }

Note: I removed the ./ from prior to the aliased path. After doing that, my paths beginning with ./ were now working, but I noticed a totally different issue. This import of something from node_modules:

import { sign } from 'jsonwebtoken';

is being transformed when using ttsc and ts-node to:

const jsonwebtoken_1 = require("../../../../../../types/jsonwebtoken");

I reverted back to the 1.6.5 version of both modules and tested again. That behavior of the import from node_modules was also incorrect in that version -- I just hadn't seen it before.

Based on that, I don't think the incorrect transformation is specific to relative paths. I believe the problem is really related to all non-aliased paths -- including imports from node_modules. I'm not quite sure why ttsc and ts-node behaved differently when ./ prefixed the aliased paths, but I'm okay with leaving that off the path since typescript works fine without it. However, the transformation for node_modules imports still looks off. In taking a peek at the commit, I suspect there might be an issue for relative paths that are . standalone or begin with ../ rather than just ./ as well.

@zerkalica
Copy link
Owner

is being transformed when using ttsc and ts-node to:
const jsonwebtoken_1 = require("../../../../../../types/jsonwebtoken");

Right, only first entry in path array used, "../node_modules/*" ignored. It's hard to resolve modules correctly and substitute right path.

    "baseUrl": ".",
    "paths": {
      "*": [
        "./src/types/*",
        "./node_modules/*"
      ]
    }

Strange paths usage, do you really need it? Why not to create project/@types/*.d.ts, like here.

However, when running through ts-node (using the ttypescript compiler) I had different results.

Can you provide example?

@shawnmcknight
Copy link
Author

shawnmcknight commented Feb 7, 2019

Right, only first entry in path array used, "../node_modules/*" ignored. It's hard to resolve modules correctly and substitute right path.

I think the idea here is that the global * path exists only to allow typescript to resolve ambient declarations that don't exist in node_modules/@types or as node_modules/module_name/index.d.ts. So what's happening now is all imports (other than those prefixed by ./ after that last commit) are getting transformed which isn't the desired outcome.

Strange paths usage, do you really need it? Why not to create project/@types/*.d.ts, like here.

I had hoped your suggestion was a solution I had missed, but it doesn't seem like typescript resolves folders named @types unless it exists inside node_modules. When I tried using project/@types/*.d.ts like your example, typescript was still giving me an error.
I pulled down the project you gave as an example and that project is getting the error too:

Could not find a declaration file for module 'globby'. 'c:/<redacted>/Projects/open-source/zerollup/node_modules/globby/index.js' implicitly has an 'any' type.
  Try `npm install @types/globby` if it exists or add a new declaration (.d.ts) file containing `declare module 'globby';`ts(7016)

The main difference between your project and mine is that my project has "noImplicitAny": true, so the error is fatal. In neither your project nor mine does it look like types stored in project/@types are being resolved by typescript.

The path layout example I gave is that indicated here, in the TypeScript-Node-Starter repo from Microsoft's Github. I have not found any other way to ensure that ambient declarations local to the project get resolved, but I'm definitely open to suggestions there.

However, when running through ts-node (using the ttypescript compiler) I had different results.
Can you provide example?

I'm not terribly concerned with that particular divergence because after removing the ./ from the aliased paths both ttsc and ts-node started behaving the same. In looking back at the linked typescript-node-starter example, they did not have ./ prefixing their paths so that was probably just something I did in error. Since removing that resolved the divergence, I'm good there.

With that said, I'll work up a small example repo to illustrate my ongoing problem.

@shawnmcknight
Copy link
Author

I've created a repo with an example of the issue I'm having. On the master branch, npm run build will produce output in the ./dist folder.

The original source of ./src/schema/example.ts includes the following:

import { Schema } from 'mongoose';
import beautifyUnique from 'mongoose-beautiful-unique-validation';
import { collation } from '#shared/configurations';

After building, you'll see that ./dist/schema/example.js has the following malformed require statements:

const mongoose_1 = require("../@types/mongoose");
const mongoose_beautiful_unique_validation_1 = __importDefault(require("../@types/mongoose-beautiful-unique-validation"));

On the other hand, the require which was aliased through #shared is perfect:

const configurations_1 = require("../shared/configurations");

If you switch to the not-resolving branch, you'll see that branch eliminates the * path reference from tsconfig.json. However, if you try to run npm run build now, it will fail with:

src/schema/example.ts:2:28 - error TS7016: Could not find a declaration file for module 'mongoose-beautiful-unique-validation'. 'C:/<redacted>/Projects/ts-transform-paths-example/node_modules/mongoose-beautiful-unique-validation/index.js' implicitly has an 'any' type.
  Try `npm install @types/mongoose-beautiful-unique-validation` if it exists or add a new declaration (.d.ts) file containing `declare module 'mongoose-beautiful-unique-validation';`

2 import beautifyUnique from 'mongoose-beautiful-unique-validation';
                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This is because without the * path, typescript can't locate the ambient declaration for mongoose-beautiful-unique-validation which is located in ./src/@types.

@zerkalica
Copy link
Owner

Ok, got it.
Better use symlinks in node_modules/@types or place declare module 'name' { ... } into your project/@types/*d.ts.

Adding "*" into tsconfig "paths" entry is a workaround, because typescript doesn't support per-module definitions outside node_modules/@types. Ts looks declare module in any d.ts in project/@types and uses module resolution strategy in project/node_modules/@types only.

DefinitelyTyped doesn't works locally. Typescript developers suggests above workarounds again and again: one, two, three

I think about it in my plugin, but it's hard to resolve d.ts / module.

@shawnmcknight
Copy link
Author

shawnmcknight commented Feb 8, 2019

I think about it in my plugin, but it's hard to resolve d.ts / module.

Since * in paths appears to be the recommended approach to handle resolution, it would seem reasonable for your plugin to simply ignore that property in the path when transforming. If the plugin didn't transform the bare * path, it would only transform those that are truly aliased. I can't think of a use case where someone would want the * path to transform.

@zerkalica
Copy link
Owner

Try 1.7.0, add exclude option to config:

{
    "compilerOptions": {
        "plugins": [
            {
                "transform": "@zerollup/ts-transform-paths",
                "exclude": ["*"]
            }
        ]
    }
}

@shawnmcknight
Copy link
Author

Looks like its doing the trick! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants