kentcdodds/babel-plugin-codegen

Cannot get past "Must module.exports a string."

Opened this issue · 11 comments

  • babel-plugin-codegen version: 3.0.0
  • node version: 10.15.0
  • npm (or yarn) version: yarn 1.13.0
  • babel: 7.4.5
  • babel-plugin-macros: 2.5.1 (required by another dep)

Relevant code or config

// my.js
codegen`module.exports = ''`;

// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/env',
      {
        targets: {
          browsers: [
            'last 2 versions',
            'ie 11',
          ]
        },
        useBuiltIns: 'usage',
        corejs: 2,
      },
    ],
  ],
  plugins: [
    'macros',
    'codegen',
    '@babel/plugin-syntax-dynamic-import',
  ],
};

What you did:

Try to compile my.js

What happened:

codegen: Must module.exports a string.

      at getReplacement (node_modules/babel-plugin-codegen/dist/helpers.js:43:11)
      at replace (node_modules/babel-plugin-codegen/dist/helpers.js:69:21)
      at asFunction (node_modules/babel-plugin-codegen/dist/replace.js:57:5)
      at asIdentifier (node_modules/babel-plugin-codegen/dist/replace.js:127:22)
      at PluginPass.Identifier (node_modules/babel-plugin-codegen/dist/index.js:37:11)
      at newFn (node_modules/@babel/traverse/lib/visitors.js:193:21)
      at NodePath._call (node_modules/@babel/traverse/lib/path/context.js:53:20)
      at NodePath.call (node_modules/@babel/traverse/lib/path/context.js:40:17)
      at NodePath.visit (node_modules/@babel/traverse/lib/path/context.js:88:12)
      at TraversalContext.visitQueue (node_modules/@babel/traverse/lib/context.js:118:16)

Reproduction repository:

Problem description:

I cannot seem to get past the Must module.exports a string. I've tried using escaped backticks, double quotes and single quotes, joining arrays, concatenating strings some other way (e.g., with + operator), etc.

What am I doing wrong?

Suggested solution:

n/a

Hi @foxbunny,

Interesting. Do you get this same error when your string is used to generate actual code?

If you could make a reproduction repository that would be very helpful.

@kentcdodds Yes. I get that with pretty much anything, string or no string.

I'll see if I can whip up a repro during the week.

@kentcdodds Here's the repo that contains the sample. Details are in the readme.

I can reproduce this. Thank you. Weird things are going on here. I'm wondering if it could be a regression bug in recent versions of babel.

I don't have time to look in it today, but I'll try this week sometime.

OK. Thanks for looking into this. I'm really excited about trying codegen. :)

I'm afraid that I don't personally have time to allocate to digging into this. Sorry :-/

No biggie. I'd look into it myself, but kinda similar situation time-wise. :)

Fi2zz commented

this issue can be reproduced when use with "@babel/preset-env":

see error stacks below

Error: ~/babel-demo/src/index.js: codegen: Must module.exports a string.
    at getReplacement (~/babel-demo/codegen/helpers.js:46:11)
    at replace (~/babel-demo/codegen/helpers.js:67:23)
    at asFunction (~/babel-demo/codegen/replace.js:144:5)
    at asIdentifier (~/babel-demo/codegen/replace.js:67:18)
    at PluginPass.Identifier (~/babel-demo/codegen/index.js:26:11)
    at newFn (~/babel-demo/node_modules/@babel/traverse/lib/visitors.js:179:21)
    at NodePath._call (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:55:20)
    at NodePath.call (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:42:17)
    at NodePath.visit (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:90:31)
    at TraversalContext.visitQueue (~/babel-demo/node_modules/@babel/traverse/lib/context.js:112:16) {
  code: 'BABEL_TRANSFORM_ERROR'
}

this line get the value with undefined:

const code = argumentsPaths[0].evaluate().value;

 function asFunction(path, fileOpts) {
    const argumentsPaths = path.get("arguments");
    // variable code  is undefined, 
    const code = argumentsPaths[0].evaluate().value;
    replace(
      {
        path: argumentsPaths[0].parentPath,
        code,
        fileOpts
      },
      babel
    );
  }
function getReplacement({ code, fileOpts, args = [] }, babel) {
  let module = requireFromString(code, fileOpts.filename);
  // If a function is epxorted, call it with args
  if (typeof module === "function") {
    module = module(...args);
  } else if (args.length) {
    throw new Error(
      `codegen module (${p.relative(
        process.cwd(),
        fileOpts.filename
      )}) cannot accept arguments because it does not export a function. You passed the arguments: ${args.join(
        ", "
      )}`
    );
  }

  // Convert whatever we got now (hopefully a string) into AST form
  if (typeof module !== "string") {
    // console.log(typeof module);
    throw new Error("codegen: Must module.exports a string.");


  }
  return babel.template(module, {
    preserveComments: true,
    placeholderPattern: false,
    ...fileOpts.parserOpts,
    sourceType: "module"
  })();
}
Fi2zz commented

this issue can be reproduced when use with "@babel/preset-env":

see error stacks below

Error: ~/babel-demo/src/index.js: codegen: Must module.exports a string.
    at getReplacement (~/babel-demo/codegen/helpers.js:46:11)
    at replace (~/babel-demo/codegen/helpers.js:67:23)
    at asFunction (~/babel-demo/codegen/replace.js:144:5)
    at asIdentifier (~/babel-demo/codegen/replace.js:67:18)
    at PluginPass.Identifier (~/babel-demo/codegen/index.js:26:11)
    at newFn (~/babel-demo/node_modules/@babel/traverse/lib/visitors.js:179:21)
    at NodePath._call (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:55:20)
    at NodePath.call (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:42:17)
    at NodePath.visit (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:90:31)
    at TraversalContext.visitQueue (~/babel-demo/node_modules/@babel/traverse/lib/context.js:112:16) {
  code: 'BABEL_TRANSFORM_ERROR'
}

this line get the value with undefined:

const code = argumentsPaths[0].evaluate().value;

 function asFunction(path, fileOpts) {
    const argumentsPaths = path.get("arguments");
    // variable code  is undefined, 
    const code = argumentsPaths[0].evaluate().value;
    replace(
      {
        path: argumentsPaths[0].parentPath,
        code,
        fileOpts
      },
      babel
    );
  }
function getReplacement({ code, fileOpts, args = [] }, babel) {
  let module = requireFromString(code, fileOpts.filename);
  // If a function is epxorted, call it with args
  if (typeof module === "function") {
    module = module(...args);
  } else if (args.length) {
    throw new Error(
      `codegen module (${p.relative(
        process.cwd(),
        fileOpts.filename
      )}) cannot accept arguments because it does not export a function. You passed the arguments: ${args.join(
        ", "
      )}`
    );
  }

  // Convert whatever we got now (hopefully a string) into AST form
  if (typeof module !== "string") {
    // console.log(typeof module);
    throw new Error("codegen: Must module.exports a string.");


  }
  return babel.template(module, {
    preserveComments: true,
    placeholderPattern: false,
    ...fileOpts.parserOpts,
    sourceType: "module"
  })();
}

seems @babel/plugin-transform-template-literals caused this issue, i have tested them one by one and combinations;

node_modules/@babel/preset-env/lib/index.js

const pluginOfPresetEnv =[];
for(let key of transformations .keys()){
  pluginOfPresetEnv.push(key)
}
console.log('pluginOfPresetEnv',pluginOfPresetEnv)
  //pluginOfPresetEnv [
    //   'transform-template-literals',
    //   'transform-literals',
    //   'transform-function-name',
    //   'transform-arrow-functions',
    //   'transform-block-scoped-functions',
    //   'transform-classes',
    //   'transform-object-super',
    //   'transform-shorthand-properties',
    //   'transform-duplicate-keys',
    //   'transform-computed-properties',
    //   'transform-for-of',
    //   'transform-sticky-regex',
    //   'transform-dotall-regex',
    //   'transform-unicode-regex',
    //   'transform-spread',
    //   'transform-parameters',
    //   'transform-destructuring',
    //   'transform-block-scoping',
    //   'transform-typeof-symbol',
    //   'transform-new-target',
    //   'transform-regenerator',
    //   'transform-exponentiation-operator',
    //   'transform-async-to-generator',
    //   'proposal-async-generator-functions',
    //   'proposal-object-rest-spread',
    //   'proposal-unicode-property-regex',
    //   'proposal-json-strings',
    //   'proposal-optional-catch-binding'
  // ]


  let pluginOfPresetEnvArray =[

        // 'transform-template-literals',
      'transform-literals',
      'transform-function-name',
      'transform-arrow-functions',
      'transform-block-scoped-functions',
      'transform-classes',
      'transform-object-super',
      'transform-shorthand-properties',
      'transform-duplicate-keys',
      'transform-computed-properties',
      'transform-for-of',
      'transform-sticky-regex',
      'transform-dotall-regex',
      'transform-unicode-regex',
      'transform-spread',
      'transform-parameters',
      'transform-destructuring',
      'transform-block-scoping',
      'transform-typeof-symbol',
      'transform-new-target',
      'transform-regenerator',
      'transform-exponentiation-operator',
      'transform-async-to-generator',
      'proposal-async-generator-functions',
      'proposal-object-rest-spread',
      'proposal-unicode-property-regex',
      'proposal-json-strings',
      'proposal-optional-catch-binding'

  ]

transformations.forEach(pluginName => {
  if(pluginOfPresetEnvArray.includes(pluginName)){
  const config  ={ spec,loose, useBuiltIns: pluginUseBuiltIns}
  plugins.push([getPlugin(pluginName), config])
  }
});
const regenerator = transformations.has("transform-regenerator");
Fi2zz commented

this issue can be reproduced when use with "@babel/preset-env":
see error stacks below

Error: ~/babel-demo/src/index.js: codegen: Must module.exports a string.
    at getReplacement (~/babel-demo/codegen/helpers.js:46:11)
    at replace (~/babel-demo/codegen/helpers.js:67:23)
    at asFunction (~/babel-demo/codegen/replace.js:144:5)
    at asIdentifier (~/babel-demo/codegen/replace.js:67:18)
    at PluginPass.Identifier (~/babel-demo/codegen/index.js:26:11)
    at newFn (~/babel-demo/node_modules/@babel/traverse/lib/visitors.js:179:21)
    at NodePath._call (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:55:20)
    at NodePath.call (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:42:17)
    at NodePath.visit (~/babel-demo/node_modules/@babel/traverse/lib/path/context.js:90:31)
    at TraversalContext.visitQueue (~/babel-demo/node_modules/@babel/traverse/lib/context.js:112:16) {
  code: 'BABEL_TRANSFORM_ERROR'
}

this line get the value with undefined:

const code = argumentsPaths[0].evaluate().value;

 function asFunction(path, fileOpts) {
    const argumentsPaths = path.get("arguments");
    // variable code  is undefined, 
    const code = argumentsPaths[0].evaluate().value;
    replace(
      {
        path: argumentsPaths[0].parentPath,
        code,
        fileOpts
      },
      babel
    );
  }
function getReplacement({ code, fileOpts, args = [] }, babel) {
  let module = requireFromString(code, fileOpts.filename);
  // If a function is epxorted, call it with args
  if (typeof module === "function") {
    module = module(...args);
  } else if (args.length) {
    throw new Error(
      `codegen module (${p.relative(
        process.cwd(),
        fileOpts.filename
      )}) cannot accept arguments because it does not export a function. You passed the arguments: ${args.join(
        ", "
      )}`
    );
  }

  // Convert whatever we got now (hopefully a string) into AST form
  if (typeof module !== "string") {
    // console.log(typeof module);
    throw new Error("codegen: Must module.exports a string.");


  }
  return babel.template(module, {
    preserveComments: true,
    placeholderPattern: false,
    ...fileOpts.parserOpts,
    sourceType: "module"
  })();
}

seems @babel/plugin-transform-template-literals caused this issue, i have tested them one by one and combinations;

node_modules/@babel/preset-env/lib/index.js

const pluginOfPresetEnv =[];
for(let key of transformations .keys()){
  pluginOfPresetEnv.push(key)
}
console.log('pluginOfPresetEnv',pluginOfPresetEnv)
  //pluginOfPresetEnv [
    //   'transform-template-literals',
    //   'transform-literals',
    //   'transform-function-name',
    //   'transform-arrow-functions',
    //   'transform-block-scoped-functions',
    //   'transform-classes',
    //   'transform-object-super',
    //   'transform-shorthand-properties',
    //   'transform-duplicate-keys',
    //   'transform-computed-properties',
    //   'transform-for-of',
    //   'transform-sticky-regex',
    //   'transform-dotall-regex',
    //   'transform-unicode-regex',
    //   'transform-spread',
    //   'transform-parameters',
    //   'transform-destructuring',
    //   'transform-block-scoping',
    //   'transform-typeof-symbol',
    //   'transform-new-target',
    //   'transform-regenerator',
    //   'transform-exponentiation-operator',
    //   'transform-async-to-generator',
    //   'proposal-async-generator-functions',
    //   'proposal-object-rest-spread',
    //   'proposal-unicode-property-regex',
    //   'proposal-json-strings',
    //   'proposal-optional-catch-binding'
  // ]


  let pluginOfPresetEnvArray =[

        // 'transform-template-literals',
      'transform-literals',
      'transform-function-name',
      'transform-arrow-functions',
      'transform-block-scoped-functions',
      'transform-classes',
      'transform-object-super',
      'transform-shorthand-properties',
      'transform-duplicate-keys',
      'transform-computed-properties',
      'transform-for-of',
      'transform-sticky-regex',
      'transform-dotall-regex',
      'transform-unicode-regex',
      'transform-spread',
      'transform-parameters',
      'transform-destructuring',
      'transform-block-scoping',
      'transform-typeof-symbol',
      'transform-new-target',
      'transform-regenerator',
      'transform-exponentiation-operator',
      'transform-async-to-generator',
      'proposal-async-generator-functions',
      'proposal-object-rest-spread',
      'proposal-unicode-property-regex',
      'proposal-json-strings',
      'proposal-optional-catch-binding'

  ]

transformations.forEach(pluginName => {
  if(pluginOfPresetEnvArray.includes(pluginName)){
  const config  ={ spec,loose, useBuiltIns: pluginUseBuiltIns}
  plugins.push([getPlugin(pluginName), config])
  }
});
const regenerator = transformations.has("transform-regenerator");

here is the key codes :
https://github.com/babel/babel/blob/master/packages/babel-plugin-transform-template-literals/src/index.js#L96

after this step, the codegen function visitor dead

Confirming this is still an issue.

That said, it can be circumvented by invoking codegen directly instead of using a tagged template:

# Instead of
codegen`module.exports = ...`

# do
codegen(`module.exports = ...`)