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

Configuration question for Mocha, Lerna, Babel, and module aliasing #1665

Closed
seanpoulter opened this issue May 18, 2018 · 7 comments
Closed

Comments

@seanpoulter
Copy link

Issue description or question

Hey Artem and team! I ❤️ WallabyJs but have recently run into configuration problems. We've recently switched to a Lerno mono-repo at work and I haven't figured out how to get things working. Could you or someone from the community please help?

It seems no matter what I try I keep ending up with an error like this:

screen shot 2018-05-18 at 18 44 44

The issue seems to be with transpiling required modules that are required using module aliases. Sometimes we're importing from the linked package like @package/packages/sub-package and sometimes like sub-package/lib.

I've set up this repo that reproduces the issue: https://github.com/seanpoulter/wallabyjs-public-1665

Thanks in advance!

Wallaby.js configuration file

module.exports = wallaby => ({
  testFramework: 'mocha',

  env: {
    type: 'node',
  },

  files: [
    'jsconfig.json',
    'packages/mocha-config/*.js',
    'packages/environment/*.js',
    'packages/app/**/*.js',

    '!packages/app/**/*.spec.js',
    '!packages/app/**/*.test.js',
    '!packages/app/node_modules/',
  ],

  tests: [
    'packages/app/**/*.spec.js',
    'packages/app/**/*.test.js',

    '!node_modules/',
    '!packages/*/node_modules/',
  ],

  compilers: {
    '**/*.js': wallaby.compilers.babel(),
    '**/*.jsx': wallaby.compilers.babel(),
  },

  setup: () => {
    // Configure module aliases
    if (!global._moduleAliasesRegistered) {
      const { delimeter, join } = require('path');
      const tsConfigPaths = require('tsconfig-paths');
      const { compilerOptions: { baseUrl, paths }} = require('./jsconfig.json');
      tsConfigPaths.register({ baseUrl, paths });
      global._moduleAliasesRegistered = true;
    }

    require('babel-polyfill');
    require('./packages/mocha-config/setup')
  },

  debug: true,
});

Code editor or IDE name and version

Visual Studio Code v1.21.1

OS name and version

OSX 10.13.4

@ArtemGovorov
Copy link
Member

@seanpoulter Thanks for providing the repo!

The issue is that when following the symlinks in your node_modules, wallaby comes to your original files (as opposed to the instrumented&compiled files in its cache that it needs to run).

This one line solution should work for mocha:

  setup: w => {
    // Configure module aliases
    require('module-alias').addAliases({ '@package': w.projectCacheDir + '/packages' });

    require('babel-polyfill');
    require('./packages/mocha-config/setup')
  },

(+ yarn add module-alias).

I have also sent your the PR.

Another way (with a bit more code, but no extra dependency, would be to patch node require in the setup to redirect all @package/* requests to w.projectCacheDir + '/packages').

Please let me know if the solution works for you.

@seanpoulter
Copy link
Author

Amazing. Thank you so much, especially for explaining the root cause with the symlinks.

Have you seen your second option to patch module.require in the wild before? I haven't found a good authoritative example of it yet. Is it as easy as wrapping the existing function?

const _require = module.require;
const mapMyPackages = id => {}; // ... find/replace for known packages
module.require = id => _require(mapMyPackages(id))

@ArtemGovorov
Copy link
Member

@seanpoulter
Copy link
Author

seanpoulter commented May 19, 2018

This works perfectly with module-alias! Thanks again Artem. 🥇

For anyone else who ends up here, if you can't use the module-alias dependency the vanilla JS solution with bonus comments to make it even longer.

  setup: () => {
    if (!global._moduleAliasesRegistered) {
      // ^^ The docs don't make it clear how often the `setup()` function is called,
      //      but the other examples make sure this is done once and only once.

      const moduleAliases = {
        '@package': `${wallaby.projectCacheDir}/packages`
      };
      // ^^ This is the only thing that will change as the package grows

      const { Module } = require('module');
      // ^^ Based on the notes in the docs when the setup function is called you don't
      // have access to variables out of scope. That's why these aren't at the top of the file.
      // See https://wallabyjs.com/docs/config/bootstrap.html#setup-function

      const pathUsesAlias = (path, alias) => path.startsWith(alias) &&
        (path[alias.length] === '/' || path.length === alias.length);
      // ^^ Instead of blindly replacing things makes sure it's a good match.
      //    That's how module-alias does it: https://github.com/ilearnio/module-alias/blob/2e40855af0a74719181a816f34a4a3f6d1232472/index.js#L46

      const mapModuleAliases = path => {
        for (const alias of Object.keys(moduleAliases)) {
          if (pathUsesAlias(path, alias)) {
            return path.replace(alias, moduleAliases[alias]);
          }
        }
        return path;
      }

      Module.prototype.require = new Proxy(Module.prototype.require, {
        apply: (target, thisArg, [path]) => target.call(thisArg, mapModuleAliases(path))
      });
      // ^^ The ES6 Proxy accepts an Object and a handler. The handler.apply property
      //    is used to trap function calls and redefine it's behaviour.
      //    For more, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/apply

      global._moduleAliasesRegistered = true;
    }

    require('./packages/mocha-config/setup');
  },

@ArtemGovorov
Copy link
Member

@seanpoulter Thanks for the update and for sharing the full vanilla JS solution Sean! Loved the idea of using Proxy, nice one.

@seanpoulter
Copy link
Author

The vanilla solution doesn't quite work when other mocking libraries also call require('module') though. 🤦‍♂️

@boneskull
Copy link

boneskull commented Aug 15, 2019

I made this work for me using the following with module-alias, adapted from @ArtemGovorov's suggestion:

setup() {
    const path = require('path');
    const fs = require('fs');
    // all subpackages are in here; you could load `lerna.json` to otherwise figure out
    // where to look
    const packagesRoot = path.join(wallaby.projectCacheDir, 'packages');
    const packages = fs.readdirSync(packagesRoot);

    // pardon the naughty reduce() here
    const aliases = packages.reduce((acc, pkg) => {
      const subpackagePath = path.join(packagesRoot, pkg);
      return {
        ...acc,
        [`@your-package-scope/${pkg}`]: path.join(subpackagePath, 'src'),
        [`@your-package-scope/${pkg}/src`]: path.join(subpackagePath, 'src'),
        [`@your-package-scope/${pkg}/test`]: path.join(subpackagePath, 'test')
      };
    }, {});
    
    require('module-alias').addAliases(aliases);
}

The problem that I was running into was that when a file requested @your-package-scope/some-package, it was using Node.js' module resolution algorithm, which looks at the main field of package.json. But the file in the main field is generated by Babel, and I want coverage against my sources.

Each subpackage has its entry point in index.js in its src/ directory, so I was able to alias @your-package-scope/some-package to @your-package-scope/some-package/src, which works fine. But if a file requested @your-package-scope/some-package/src/foo.js, this would not work--the alias would end up looking like @your-package-scope/some-package/src/src/foo.js, which is non-existent. I needed to have a more specific matcher for this case (and for test files, which also can require fixtures in other subpackages).

The above can probably be rewritten more elegantly using module-alias's support for functions (moduleAlias.addAlias('@src', (fromPath, request, alias) => { ... }).

@ArtemGovorov ArtemGovorov changed the title Configuration question for Lerna, Babel, and module aliasing Configuration question for Mocha, Lerna, Babel, and module aliasing Aug 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants