Skip to content

babel-plugin-relay should support ecmascript module importing #2706

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

Open
ckknight opened this issue Apr 9, 2019 · 6 comments
Open

babel-plugin-relay should support ecmascript module importing #2706

ckknight opened this issue Apr 9, 2019 · 6 comments
Labels

Comments

@ckknight
Copy link
Contributor

ckknight commented Apr 9, 2019

When using babel-plugin-relay, graphql template tag calls are converted into a function that uses require to load another file.

It would be nice to be able to opt-in to using an ECMAScript module-friendly import statement rather than relying on the commonjs require.

This would ideally make it easier to integrate into tools like rollup.

@ckknight
Copy link
Contributor Author

ckknight commented Apr 9, 2019

Alright, I got this working locally.

I ripped apart babel-plugin-relay and took only the parts I needed (modern-only, no hash checking, simple regex to find the name instead of full graphql parse).

The meat of the change is in createModernNode which uses t.importDeclaration and the like.

'use strict';

function getValidGraphQLTag(path) {
  const tag = path.get('tag');

  if (!tag.isIdentifier({ name: 'graphql' })) {
    return null;
  }

  const quasis = path.node.quasi.quasis;

  if (quasis.length !== 1) {
    throw new Error(
      'BabelPluginRelay: Substitutions are not allowed in graphql fragments. ' +
        'Included fragments should be referenced as `...MyModule_propName`.',
    );
  }

  const text = quasis[0].value.raw;

  const match = /^\s*(query|mutation|subscription|fragment)\s*(\w+)/.exec(text);
  if (!match) {
    throw new Error('Unable to parse ' + text);
  }

  return { type: match[1], name: match[2] };
}

/**
 * Given a graphql`` tagged template literal, replace it with the appropriate
 * runtime artifact.
 */
function compileGraphQLTag(t, path, state, type, name) {
  return replaceMemoized(t, path, createAST(t, state, path, type, name));
}

function createAST(t, state, path, type, name) {
  const isHasteMode = Boolean(state.opts && state.opts.haste);
  const isDevVariable = state.opts && state.opts.isDevVariable;
  const artifactDirectory = state.opts && state.opts.artifactDirectory;
  const buildCommand =
    (state.opts && state.opts.buildCommand) || 'relay-compiler';

  // Fallback is 'true'
  const isDevelopment =
    (process.env.BABEL_ENV || process.env.NODE_ENV) !== 'production';

  const modernNode = createModernNode(t, path, type, name, state, {
    artifactDirectory,
    buildCommand,
    isDevelopment,
    isHasteMode,
    isDevVariable,
  });
  return modernNode;
}

function getTopScope(path) {
  let topScope = path.scope;
  while (topScope.parent) {
    topScope = topScope.parent;
  }
  return topScope;
}

function replaceMemoized(t, path, ast) {
  const topScope = getTopScope(path);

  if (path.scope === topScope) {
    path.replaceWith(ast);
  } else {
    const id = topScope.generateDeclaredUidIdentifier('graphql');
    path.replaceWith(
      t.logicalExpression('||', id, t.assignmentExpression('=', id, ast)),
    );
  }
}

const GENERATED = './__generated__/';

function createModernNode(t, path, type, definitionName, state, options) {
  const requiredFile = definitionName + '.graphql';
  const requiredPath = options.isHasteMode
    ? requiredFile
    : options.artifactDirectory
    ? getRelativeImportPath(state, options.artifactDirectory, requiredFile)
    : GENERATED + requiredFile;

  const topScope = getTopScope(path);
  const nodeVariable = topScope.generateUidIdentifier(definitionName);
  const importDefaultSpecifier = t.importDefaultSpecifier(nodeVariable);
  const importDeclaration = t.importDeclaration(
    [importDefaultSpecifier],
    t.stringLiteral(requiredPath),
  );
  topScope.path.unshiftContainer('body', importDeclaration);

  const bodyStatements = [t.returnStatement(nodeVariable)];
  return t.functionExpression(null, [], t.blockStatement(bodyStatements));
}

function BabelPluginRelay(context) {
  const { types: t } = context;
  if (!t) {
    throw new Error(
      'BabelPluginRelay: Expected plugin context to include "types", but got:' +
        String(context),
    );
  }

  const visitor = {
    TaggedTemplateExpression(path, state) {
      // Convert graphql`` literals
      const tag = getValidGraphQLTag(path);
      if (tag) {
        compileGraphQLTag(t, path, state, tag.type, tag.name);
      }
    },
  };

  return {
    visitor: {
      Program(path, state) {
        path.traverse(visitor, state);
      },
    },
  };
}

module.exports = BabelPluginRelay;

@jsphstls
Copy link

Thanks @ckknight , I was trying this out but getRelativeImportPath is not defined.

@stale
Copy link

stale bot commented Dec 25, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Dec 25, 2020
@theseyi
Copy link

theseyi commented Jan 3, 2021

Has anyone found a sustainable solution to this? Started a new project with rollup plus snowpack and trying out relay and ran into this exact same issue :( Seems there's only appetite to support bundlers like Webpack for this library. I believe this issue #2445 is also related

@TrySound
Copy link
Contributor

TrySound commented Jan 3, 2021

I added eagerESModules option to relay config some time ago. Please try it.

@stale
Copy link

stale bot commented Jan 9, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jan 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants