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

What is the resolution for these types of errors: <addon> is trying to import <peerDep> from within its app tree. This is unsafe, because <addon> can't control which dependencies are resolvable from the app #1062

Closed
NullVoxPopuli opened this issue Dec 22, 2021 · 7 comments

Comments

@NullVoxPopuli
Copy link
Collaborator

NullVoxPopuli commented Dec 22, 2021

Note: this is within a v2 addon / normal npm package.

I've reported / asked about this error before in Discord, and I don't remember what came of it, so I'm going to try some things and log my progress here in this issue.

Generically (for me to copy-paste to the title):

<addon> is trying to import <peerDep> from within its app tree. 
This is unsafe, because <addon> can't control which dependencies are resolvable from the app

Specifically, for me:

Thread Loader (Worker 0)
$TMPDIR/embroider/1e0772/instance-initializers/ember-statechart-component.js/ember-statechart-component.js: 
ember-statechart-component is trying to import xstate from within its app tree. 
This is unsafe, because ember-statechart-component can't control which dependencies are resolvable from the app

I know initializers are nefarious, and here is how I'm abusing them:

import { setComponentManager, setComponentTemplate } from '@ember/component';
import { hbs } from 'ember-cli-htmlbars';
import { StateNode } from 'xstate';
import ComponentManager from './-private/statechart-manager';

// Managers are managed globally, and not per app instance
setComponentManager((owner) => ComponentManager.create(owner), StateNode.prototype);
setComponentTemplate(hbs`{{yield this.state this.send}}`, StateNode.prototype);

export function initialize(): void {}

export default {
  initialize,
};

I know that the initializer is part of the app tree, and it imports xstate, as does ./-private/statechart-manager, so there is no getting around that.

I'm going to try to eliminate the addon's app tree entirely by moving the contents of my initializer to a setup method in the addon tree.

@NullVoxPopuli
Copy link
Collaborator Author

NullVoxPopuli commented Dec 22, 2021

after trying that, it seems that I get multiple copies of xstate in my tree... which, when working with the componentManager api, ends up making everything broken with errors like this:

Error: Attempted to load a component, but there wasn't a component manager associated with the definition. 
The definition was: StateNode

NullVoxPopuli added a commit to NullVoxPopuli/ember-statechart-component that referenced this issue Dec 22, 2021
@ef4
Copy link
Contributor

ef4 commented Dec 23, 2021

Move the implementation into your addon instead, and in the app tree have only a reexport of the code from the addon.

This is the same thing the classic blueprint does if you ask it to generate an initializer.

@NullVoxPopuli
Copy link
Collaborator Author

NullVoxPopuli commented Dec 23, 2021

I gave that a go here: NullVoxPopuli/ember-statechart-component#163
But now I always have duplicate files (even in classic builds):
image
(I have source maps turned off)

Consistency is nice tho. 🙃

Here is the rollup config I have so far:

import { nodeResolve } from '@rollup/plugin-node-resolve';

import { babel } from '@rollup/plugin-babel';

import { Addon } from '@embroider/addon-dev/rollup';

const addon = new Addon({
  srcDir: 'src',
  destDir: 'dist',
});

const extensions = ['.js', '.ts'];

const transpilation = [
  nodeResolve({ resolveOnly: ['./'], extensions }),
  babel({ babelHelpers: 'bundled', extensions }),
  addon.dependencies(),
  addon.hbs(),
];

export default [
  {
    input: 'src/index.ts',
    output: { ...addon.output(), entryFileNames: '[name].js' },
    plugins: [
      ...transpilation,
    ]
  },
  {
    input: 'src/registration.ts',
    output: { ...addon.output(), entryFileNames: 'instance-initializers/setup-ember-statechart-component.js' },
    plugins: [
    // These are the modules that users should be able to import from your
    // addon. Anything not listed here may get optimized away.
    addon.publicEntrypoints(['instance-initializers/*.js']),

    // These are the modules that should get reexported into the traditional
    // "app" tree. Things in here should also be in publicEntrypoints above, but
    // not everything in publicEntrypoints necessarily needs to go here.
    addon.appReexports(['instance-initializers/setup-ember-statechart-component.js']),
      ...transpilation,

    ]
  }
];

@ef4
Copy link
Contributor

ef4 commented Dec 23, 2021

(Aside: that custom component manager for xstate is slick!)

Maybe the duplication is because of your extra entryFileNames in output?

@NullVoxPopuli
Copy link
Collaborator Author

(Aside: that custom component manager for xstate is slick!)

Thanks! I really enjoy that I can do this

use createMachine and then... just render the return value

    test('it works with invoked machines', async function (assert) {
      // Invoked child machine
      const minuteMachine = createMachine({
        initial: 'active',
        states: {
          active: {
            on: {
              DECLARE_DONE: 'finished',
            },
          },
          finished: { type: 'final' },
        },
      });

      const parentMachine = createMachine({
        id: 'parent',
        initial: 'pending',
        states: {
          pending: {
            invoke: {
              id: 'timer',
              src: minuteMachine,
              onDone: 'timesUp',
            },
          },
          timesUp: {
            type: 'final',
          },
        },
      });

      this.setProperties({
        parentMachine,
      });

      await render(hbs`
        <this.parentMachine as |state send|>
          <div id="parent">{{state.value}}</div>
          <div id="child">{{state.children.timer.state.value}}</div>

          <button {{on 'click' (fn state.children.timer.send 'DECLARE_DONE')}}>
            Declare invoked machine as Done
          </button>
          {{log state}}
        </this.parentMachine>
      `);

      assert.dom('#parent').containsText('pending');
      assert.dom('#child').containsText('active');

      await click('button');

      assert.dom('#parent').containsText('timesUp');
      assert.dom('#child').hasNoText('the machine destroyed itself (on purpose)');
    });

Maybe the duplication is because of your extra entryFileNames in output?

I'll play around with output (I haven't actually looked at what addon.output() returns) -- without the entryFileNames though, my output files don't have extensions

@ef4
Copy link
Contributor

ef4 commented Dec 23, 2021

Oh, I also only just noticed that you've got two separate rollup configs. That is going to duplicate any dependencies that are used by both. Instead I think you want one config with multiple inputs.

It sounds like I need to test and adjust the default rollup config under typescript so you can stop being confused by it. It's supposed to handle all the inputs and outputs for you as long as you declare your publicEntrypoints.

@NullVoxPopuli
Copy link
Collaborator Author

two separate rollup configs.

yeah, I switched to two separate rollup configs because I couldn't figure out how to do an instance-initializer for the app-js re-exports without re-working my addon's files to match the old file/folder structure.
My package's files / entrypoints are only an index.ts and registration.ts -- the index.ts doesn't do anything with xstate, it's only a single exported ember-helper (for getting services with their type). registration.ts is technically an instance-initializer, and it imports the rest of my files (and xstate) -- I've been wanting two files in the output for build-optimization purposes -- the index.ts can go totally unused, but the registration / instance-initializer file is required, and it's "nice", I guess, to not have to have the consuming app worry about bundling all my little files when I can pre-bundle them.

It sounds like I need to test and adjust the default rollup config under typescript so you can stop being confused by it. It's supposed to handle all the inputs and outputs for you as long as you declare your publicEntrypoints.

This'd be much appreciated. Thar be dragons!

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