azu / commonjs-to-es-module-codemod

Codemod that convert CommonJS(require/exports) to ES Modules(import/export) for JavaScript/TypeScript
MIT License
81 stars 15 forks source link

Improve the `module.exports =` transformation #22

Open kassiansun opened 3 months ago

kassiansun commented 3 months ago

To ease the migration of const { xx } = require('xx'} codes, I wrote a new script based on this repository to migrate module.exports = to two types of export:

The code was tested against my own repo, I don't know much of jscodeshift, so not sure whether there are any other edge cases to handle.

const isTopNode = (j, path) => j.Program.check(path.parent.value);

function transformModuleExports(file, api, options) {
    const j = api.jscodeshift;
    const _isTopNode = (path) => isTopNode(j, path);

    // ------------------------------------------------------------------ SEARCH
    const nodes = j(file.source)
        .find(j.ExpressionStatement, {
            expression: {
                left: {
                    object: {
                        name: "module"
                    },
                    property: {
                        name: "exports"
                    }
                },
                operator: "="
            }
        })
        .filter(_isTopNode);

    if (nodes.length > 1) {
        console.error(
            "There should not be more than one `module.exports` declaration in a file. Aborting transformation"
        );
        return file.source;
    }

    console.log(`${nodes.length} nodes will be transformed`);

    // ----------------------------------------------------------------- REPLACE
    const replace = (path) => {
        const node = path.node;
        const init = node.expression.right;
        const directExports = init.properties.filter(p => p.value.type === 'Identifier');
        const indirectExports = init.properties.filter(p => p.value.type !== 'Identifier');

        if (init.type === 'ObjectExpression') {
            const newNodes = []
            if (directExports.length > 0) {
                const newNode = j.exportNamedDeclaration(null, directExports.map(p => {
                    return j.exportSpecifier.from({ exported: p.key, local: p.value })
                }));
                newNode.comments = node.comments;
                newNodes.push(newNode);
            }
            if (indirectExports.length > 0) {
                const namedExportNodes = indirectExports.map(p => {
                    const declaration = j.variableDeclaration("const", [j.variableDeclarator(p.key, p.value)]);
                    return j.exportNamedDeclaration(declaration);
                });
                newNodes.push(...namedExportNodes);
            }
            return newNodes;
        } else {
            const newNode = j.exportDefaultDeclaration(init);
            newNode.comments = node.comments;
            return newNode;
        }
    };
    return nodes.replaceWith(replace).toSource();
}

const transformScripts = (fileInfo, api, options) => {
    return [transformModuleExports].reduce((input, script) => {
        return script(
            {
                source: input
            },
            api,
            options
        );
    }, fileInfo.source);
};

module.exports = transformScripts;

BTW, for any other who wants to use this script directly, just run it against jscodeshift as usual: npx jscodeshift -t migrate.js