rnx-kit icon indicating copy to clipboard operation
rnx-kit copied to clipboard

Importing files outside root directory while using metro-plugin-typescript

Open karimb11 opened this issue 1 year ago • 10 comments

What happened?

When trying to use files outside the current root project (Using babel module-resolver aliases), this error occurrs:

error: Error: Cannot find project root for source file PATH_TO_PROJECT\shared\index.ts' at findProjectRoot (PATH_TO_PROJECT\mobile\node_modules@rnx-kit\metro-plugin-typescript\lib\projectCache.js:58:19) at Object.getProjectInfo (PATH_TO_PROJECT\mobile\node_modules@rnx-kit\metro-plugin-typescript\lib\projectCache.js:112:22)

PATH_TO_PROJECT is just a placeholder for my actual full path.

For information, using metro without rnx-kit works

Affected Package

@rnx-kit\metro-plugin-typescript

Version

0.4.4

Which platforms are you seeing this issue on?

  • [X] Android
  • [X] iOS
  • [ ] macOS
  • [ ] Windows

System Information

This is a pure JavaScript issue.

Steps to Reproduce

  • Import a file that is outside the current project's root directory, using babel's module-resolver plugin "alias" option.

Code of Conduct

  • [X] I agree to follow this project's Code of Conduct

karimb11 avatar Feb 05 '24 02:02 karimb11

I did this rough hack to get it to work, just defaulted everything to the current project, since there is only one right now.

function createProjectCache(print) {
    const documentRegistry = typescript_1.default.createDocumentRegistry();
    const diagnosticWriter = (0, typescript_service_1.createDiagnosticWriter)(print);
    // Collection of projects organized by root directory, then by platform.
    const projects = {};
    function findProjectRoot(sourceFile) {
        // Search known root directories to see if the source file is in one of them.
        for (const root of Object.keys(projects)) {
--->        return root;
            if (sourceFile.startsWith(root)) {
                return root;
            }

Supporting shared modules seems like an important feature, I think this issue should be prioritized.

Thanks 🙏

karimb11 avatar Feb 05 '24 02:02 karimb11

What does your tsconfig.json look like? Does it match with how you've configured module-resolver?

tido64 avatar Feb 05 '24 18:02 tido64

@tido64 Yes the aliases are similar, the proof is that when I added the hack, the build worked correctly. Also the logic that exists in createProjectCache() makes it clear why this fails. You have a file that is outside the root directory which is being compared with the the root directory using .startsWith. That check will always fail.

For info, this issue does not occur with stock Metro bundler.

karimb11 avatar Feb 05 '24 19:02 karimb11

@tido64 You can validate this issue by creating a very small project that imports a file that is outside the root directory, using a path alias (Configured in both tsconfig.json and babel config)

karimb11 avatar Feb 05 '24 20:02 karimb11

Example babel configuration

module.exports = {
    presets: [ "@rnx-kit/babel-preset-metro-react-native" ],

    plugins: [
        [
            "module-resolver",
            {
                root: [ "." ],

                extensions: [ ".ts", ".tsx", ".json" ],

                alias: {
                    "shared": "../shared"
                }
            }
        ]
    ]
};

Example tsconfig configuration

{
    "extends": "@react-native/typescript-config/tsconfig.json",

    "compilerOptions": {
        "baseUrl": "./src",

        "paths": {
            "shared/*": [ "../../shared/*" ]
        }
    },

    "include": [
        "./src",
        "../shared"
    ],

    "exclude": [
        "./node_modules"
    ]
}

karimb11 avatar Feb 05 '24 20:02 karimb11

Am I understanding you correctly if I say you have a structure that looks something like:

root
├── app1
│   ├── <.js files>
│   └── package.json
├── app2
│   ├── <.js files>
│   └── package.json
└── shared
    └── <loose .js files>

Where root is not a monorepo, and shared is not a package (i.e. no package.json)? In that case, could you make shared a package by adding a package.json?

The error you're seeing above is because the loose files do not belong to a package, which means that TypeScript can't cache it. I'm not sure of the consequences of adding it to a random root like you've suggested. This will take time to investigate, and it's an edge case I'm not sure we want to support. Adding a package.json should fix the issue you're seeing. If that doesn't work then we have a bigger issue at hand.

tido64 avatar Feb 06 '24 13:02 tido64

I agree with your assessement, though I assumed that it wouldn't be a random root since there is always a single root project, no? in terms of where we start the bundler from.

Either way, thank you 🙏, your suggestion has cleared the error, however will the babel and tsconfig that exist in the root project be used to process shared code as well or will the system try to lookup tsconfig files upstream?

karimb11 avatar Feb 06 '24 23:02 karimb11

As far as I can tell, each root folder is treated as a project. If a folder contains a tsconfig.json, it is treated as a TS project and its files will be type-checked by the TypeScript service. Otherwise, the folder is treated as a JS project and will not be type-checked.

cc @afoxman in case I'm mistaken.

tido64 avatar Feb 07 '24 07:02 tido64

@tido64 I see, what strategy would one have to follow to have the same tsconfig apply to these shared projects, without having to duplicate the file twice?

karimb11 avatar Feb 07 '24 18:02 karimb11

Use shared configs: https://www.typescriptlang.org/tsconfig#extends

tido64 avatar Feb 07 '24 18:02 tido64