yuanqing / create-figma-plugin

:battery: The comprehensive toolkit for developing plugins and widgets for Figma and FigJam
https://yuanqing.github.io/create-figma-plugin/
MIT License
948 stars 91 forks source link

Could not resolve imported module #238

Closed davestewart closed 3 months ago

davestewart commented 3 months ago

I'm trying to compile using a local npm module I'm working on.

I symlinked the module using npm link in the module's folder, then npm link figma-messaging in the figma plugin.

The module is there, and I can ls it:

ls -l node_modules/figma-messaging
lrwxr-xr-x  1 dave  staff  30 Jul 26 16:15 node_modules/figma-messaging -> ../../../Figma/figma-messaging

My IDE can see it fine, and I can import and use.

But when I build:

info Building...
error esbuild error
    Build failed with 1 error:
    src/app/main.ts:7:29: ERROR: Could not resolve "figma-messaging"
info Typechecking...

I have tried a few things with custom esbuild config as suggested in the docs, but no luck.

Any ideas?

davestewart commented 3 months ago

With a bit of help from Claude AI, I added the logLevel: 'debug' setting to the esbuild config and got the following:

info Building...
● [DEBUG] Resolving import "figma-messaging" in directory "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/src/app" of type "import-statement"

  Checking for package alias matches
    Failed to find any package alias matches
  Read 3 entries for directory "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/src/app"
  Searching for "figma-messaging" in "node_modules" directories starting from
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/src/app"
    Parsed package name "figma-messaging" and package subpath "."
    Checking for a package in the directory
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging"
    Read 181 entries for directory "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules"
    Resolved symlink "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging" to
  "/Volumes/Data/Work/OpenSource/Figma/figma-messaging"
    The file "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/package.json"
  exists
    Read 16 entries for directory
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging"
    Attempting to load "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging" as
  a file
      Checking for file "figma-messaging"
      Checking for file "figma-messaging.jsx"
      Checking for file "figma-messaging.js"
      Checking for file "figma-messaging.tsx"
      Checking for file "figma-messaging.ts"
      Checking for file "figma-messaging.css"
      Checking for file "figma-messaging.json"
      Failed to find file "figma-messaging"
    Attempting to load "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging" as
  a directory
      Read 16 entries for directory
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging"
      Searching for main fields in
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/package.json"
      Failed to find file
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/index.jsx"
      Failed to find file
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/index.js"
      Failed to find file
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/index.tsx"
      Failed to find file
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/index.ts"
      Failed to find file
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/index.css"
      Failed to find file
  "/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/node_modules/figma-messaging/index.json"

✘ [ERROR] Could not resolve "figma-messaging"

    src/app/main.ts:7:29:
      7 │ import { makeBus, ipc } from 'figma-messaging'
        ╵                              ~~~~~~~~~~~~~~~~~

  The "main" field here was ignored. Main fields must be configured explicitly when using the
  "neutral" platform.

    node_modules/figma-messaging/package.json:7:2:
      7 │   "main": "dist/index.js",
        ╵   ~~~~~~

  You can mark the path "figma-messaging" as external to exclude it from the bundle, which will
  remove this error and leave the unresolved path in the bundle.

1 error
error esbuild error
    Build failed with 1 error:
    src/app/main.ts:7:29: ERROR: Could not resolve "figma-messaging"
info Typechecking...

Claude said of this:

Thank you for providing the verbose output. This error is actually quite informative. The issue is related to how esbuild is interpreting the "main" field in your package.json file.

The problem is that esbuild is running in "neutral" platform mode, which means it's not automatically picking up the "main" field from package.json. This is a safety feature to ensure consistent behavior across different environments. To resolve this, you have a couple of options:

In the consuming package (where you're using esbuild), you can explicitly tell esbuild which file to use for the "figma-messaging" package. Add this to your esbuild configuration:

esbuild.build({
  // other options...
  mainFields: ['main', 'module'],
  // or if you're using the ESM version:
  // mainFields: ['module', 'main'],
})

This tells esbuild to look for the 'main' field (or 'module' field) in package.json files when resolving packages. Alternatively, you can specify the platform in your esbuild configuration:

esbuild.build({
  // other options...
  platform: 'node', // or 'browser', depending on your target environment
})

This will make esbuild behave more like the standard Node.js or browser environment when resolving modules. If neither of these work, you can explicitly set the entry point for the package:

esbuild.build({
  // other options...
  alias: {
    'figma-messaging': 'figma-messaging/dist/index.js'
  }
})

I've gone with the third option and it works.

davestewart commented 3 months ago

OK, a bit more learning from my end, and I now understand the impact of the platform: "neutral" setting:

// buildOptions
{
    bundle: true,
    logLevel: 'silent',
    minify: false,
    outfile: '/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter/build/main.js',
    platform: 'neutral',
    plugins: [],
    stdin: {
      contents: '\n' +
        "    const modules = {'src/app/main.ts--default':require('./src/app/main.ts')['default']};\n" +
        "    const commandId = (true || typeof figma.command === 'undefined' || figma.command === '' || figma.command === 'generate') ? 'src/app/main.ts--default' : figma.command;\n" +
        '    modules[commandId]();\n' +
        '  ',
      resolveDir: '/Volumes/Data/Work/OpenSource/3rdParty/figma-variables-starter'
    },
    target: 'es2017'
  }

ESBuild's docs mention:

When the platform is set to neutral:

The main fields setting is empty by default. If you want to use npm-style packages, you will likely have to configure this to be something else such as main for the standard main field used by node.

Changing the platform to browser allows the module to be resolved OK.

I'm not so up on the mechanics of bundling (other than config and bug fixing) but is there any reason the platform is set to neutral if the environment will effectively always be the browser (at least, Figma's sandboxed version of it) ?

Would love to hear your comments on this, even just to further my knowledge.

davestewart commented 3 months ago

OK... have FINALLY got this to work without having to modify Create Figma Plugin's esbuild options.

In my module, I needed to configure the package.json "exports" setting:

{
  "name": "figma-messaging",
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "files": ["dist"]
}

I have elected to keep this esm-only, as I'm fairly sure everyone will be using modern build systems.

FWIW I am using tsup to compile, and the build settings for that are:

import { defineConfig } from 'tsup'

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['esm'],
  sourcemap: true,
  clean: true,
  dts: true,
  external: [
    '@figma/plugin-typings',
  ],
})

Closing this now, but leaving here for future travellers.

👍

yuanqing commented 3 months ago

@davestewart – Thanks for writing this up! Maybe the platform config should have been set to browser; definitely needs further investigation. IIRC there was some nuance with how code was being output if browser is set that caused me to use the neutral option instead

davestewart commented 3 months ago

No problem.

If you need any help, let me know.

FWIW, I think the build config docs could do with being promoted from being buried in Recipes to somewhere in Config, or a Setup section?

Only because Recipes feels (by location) like tasks you might do once you're up and running.