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

🐛 expo - Knip fails to run in an Expo app with a configurable app.config.js #919

Closed
6 tasks done
ronbraha opened this issue Jan 20, 2025 · 12 comments
Closed
6 tasks done
Labels
bug Something isn't working

Comments

@ronbraha
Copy link

Prerequisites

Reproduction url

https://github.com/ronbraha/knip-expo-issue

Reproduction access

  • I've made sure the reproduction is publicly accessible

Description of the issue

Since version 5.42.0, Knip fails to run in an expo app with an app.config.js that accepts a function with a config object.
The errors vary from version 5.42.0 to the latest (5.42.2), as there was an attempt to address this in the newest version.

This is what I see in 5.42.2 with a new app created with create-expo-app, and I experience the same issue in a production app:

module.exports = ({ config }) => config;
                    ^

TypeError: Cannot destructure property 'config' of 'undefined' as it is undefined.
    at module.exports (/Users/ronbraha/Code/debugging/knip-issue/app.config.js:1:21)
    at Object.resolveEntryPaths (file:///Users/ronbraha/Code/debugging/knip-issue/node_modules/knip/dist/plugins/expo/index.js:12:60)
    at runPlugin (file:///Users/ronbraha/Code/debugging/knip-issue/node_modules/knip/dist/WorkspaceWorker.js:246:85)
    at async WorkspaceWorker.findDependenciesByPlugins (file:///Users/ronbraha/Code/debugging/knip-issue/node_modules/knip/dist/WorkspaceWorker.js:281:13)
    at async main (file:///Users/ronbraha/Code/debugging/knip-issue/node_modules/knip/dist/index.js:120:41)
    at async run (file:///Users/ronbraha/Code/debugging/knip-issue/node_modules/knip/dist/cli.js:26:83)
    at async file:///Users/ronbraha/Code/debugging/knip-issue/node_modules/knip/dist/cli.js:104:1
@ronbraha ronbraha added the bug Something isn't working label Jan 20, 2025
@webpro
Copy link
Collaborator

webpro commented Jan 20, 2025

Thanks @ronbraha.

Yeah after I found out that it can be a function indeed I added a fix in 5.42.2.

The provided rep(r)o has a app.json - does that give issues as well?

Either way, any chance you're up for a PR to fix this? Apparently my attempt was too naive to fix all cases:

const expoConfig = typeof localConfig === 'function' ? localConfig() : localConfig;
const config = 'expo' in expoConfig ? expoConfig.expo : expoConfig;

@ronbraha
Copy link
Author

I can point out the problem and say it occurs because the referenced code here tries to call localConfig as a function but doesn't pass the options parameter containing the config object. How would Knip access this configuration object if you want to parse it? Still, it's possible that an empty object like calling localConfig({ config: /* pass the "app.json" here? */ }) may have performed better, as now the problem is that destructuring the config field from an undefined parameter crashes.

@webpro
Copy link
Collaborator

webpro commented Jan 20, 2025

The localConfig in the Knip Expo plugin is whatever resolves to the default export of app.json or app.config.js.

Would be great if you could either 1) debug that using Knip in the production app, or 2) create a an actual reproduction of that situation I could look into.

@jerone
Copy link
Contributor

jerone commented Jan 26, 2025

Experiencing the same issue.

The first parameter in the app config function is of type ConfigContext, which is required and not supposed to be undefined.
See documentation: https://docs.expo.dev/workflow/configuration/#using-typescript-for-configuration-appconfigts-instead-of-appconfigjs

@webpro
Copy link
Collaborator

webpro commented Jan 27, 2025

Without a reproduction it's cumbersome to try and help out, but here's an attempt. Would be great if you could try it out and install this version of Knip:

npm i -D https://pkg.pr.new/knip@585d7a6

@ice-cap0
Copy link

just had same issue, PR above works for me

@christophemenager
Copy link

@webpro your fix works for me in my expo project!

@ronbraha
Copy link
Author

ronbraha commented Jan 28, 2025

Thanks for the quick fix!
I noticed an issue when testing this locally: trying to iterate through the "plugins" array crashes. Logging the config while Knip is running shows that the config is an empty object.

Analyzing workspace packages/native...
{}
root/app.config.js:59
        plugins: [...config.plugins, getDummyPluginConfig()],
                            ^

TypeError: config.plugins is not iterable
    at module.exports (root/app.config.js:59:29)
    at getConfig (file://root/node_modules/knip/dist/plugins/expo/helpers.js:11:60)
    at Object.resolveEntryPaths (file://root/node_modules/knip/dist/plugins/expo/index.js:13:20)
    at runPlugin (file://root/node_modules/knip/dist/WorkspaceWorker.js:247:85)
    at async WorkspaceWorker.findDependenciesByPlugins (file:///node_modules/knip/dist/WorkspaceWorker.js:282:13)
    at async main (file://root/node_modules/knip/dist/index.js:119:41)
    at async run (file://root/node_modules/knip/dist/cli.js:26:83)
    at async file://root/node_modules/knip/dist/cli.js:104:1

Node.js v20.10.0

For additional context, if the app.config.js returns a function, that configContext.config is the parsed JSON object from app.json. I think it should be a safe assumption to try importing app.json and passing it along to app.config.js function. Are you able to add this behavior in case you iterate through app.config.js and it returns a function?

Here's an example of a "production" app.json

// app.json
{
    "expo": {
        "name": "MyApp",
        "slug": "native",
        "orientation": "portrait",
        "newArchEnabled": true,
        "icon": "./assets/icon.png",
        "userInterfaceStyle": "light",
        "splash": {
            "image": "./assets/splash.png",
            "resizeMode": "cover"
        },
        "scheme": "com.myapp.id",
        "assetBundlePatterns": ["**/*"],
        "ios": {
            "associatedDomains": [
                "applinks:app.domain.com",
                "applinks:app.staging.com"
            ],
            "supportsTablet": true,
            "googleServicesFile": "path/to/GoogleServices.plist",
            "bundleIdentifier": "com.myapp.id",
            "entitlements": {
                "aps-environment": "production"
            }
        },
        "android": {
            "permissions": [
                "com.google.android.some-permission",
                "android.permission.dummy"
            ],
            "adaptiveIcon": {
                "foregroundImage": "./assets/adaptive-icon.png",
                "backgroundColor": "#ffffff"
            },
            "googleServicesFile": "path/to/google-services.json",
            "package": "com.myapp.id",
            "intentFilters": [
                {
                    "action": "VIEW",
                    "autoVerify": true,
                    "data": [
                        {
                            "scheme": "https",
                            "host": "app.myapp.com"
                        },
                        {
                            "scheme": "https",
                            "host": "app.staging.com"
                        }
                    ],
                    "category": ["BROWSABLE", "DEFAULT"]
                }
            ]
        },
        "web": {
            "favicon": "./assets/favicon.png"
        },
        "plugins": [
            "./plugins/some-plugin.js",
            "./plugins/another-plugin.js",
            "@some-package",
            [
                "expo-build-properties",
                {
                    "ios": {
                        "useFrameworks": "static"
                    }
                }
            ]
        ]
    }
}

Here's an example of app.config.js

// app.config.js

// app.config.js schema: https://docs.expo.dev/versions/latest/config/app/
module.exports = ({ config }) => {
    return {
        ...config,
        version: APP_VERSION,
        ios: {
            ...config.ios,
            buildNumber: APP_BUILD_NUMBER,
        },
        android: {
            ...config.android,
            versionCode: 7523649324,
        },
        plugins: [...config.plugins, getDummyPluginConfig()],
        extra: {
            environment: getEnvironment(),
        },
    };
};

feel free to use my attached repo reproduction as a baseline!

@webpro
Copy link
Collaborator

webpro commented Jan 28, 2025

noticed an issue when testing this locally: trying to iterate through the "plugins" array crashes

Ah I'll fix that up and fall back to an empty array.

For additional context, if the app.config.js returns a function, that configContext.config is the parsed JSON object from app.json.

I know, but app.json is parsed as well anyway, so my guess was that things combined should end up with the same results, unless config could "outrule" or disable dependencies or entry files.

feel free to use my attached repo reproduction as a baseline!

https://knip.dev/guides/issue-reproduction#alternatives

@webpro webpro closed this as completed in 2d0876b Jan 28, 2025
@webpro
Copy link
Collaborator

webpro commented Jan 28, 2025

🚀 This issue has been resolved in v5.43.6. See Release 5.43.6 for release notes.

Using Knip in a commercial project? Please consider becoming a sponsor.

@webpro
Copy link
Collaborator

webpro commented Jan 28, 2025

Hopefully things are better now! For any new issues, please open a new ticket.

@ronbraha
Copy link
Author

I can confirm that this works! thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants