Trident D'Gao
Trident D'Gao

Reputation: 19762

How can I rewrite/repack code written for AMD to CommonJs without bundling it?

I have a project written for browser using AMD modules, now I need to run the same code in nodejs, so I need to rewrite each file into using CommonJs modules instead. I tried webpack, but it gives me a bundle which I don't need. All I want is to keep my files like they are but rewrite define(.. imports to require(..)

Upvotes: 2

Views: 377

Answers (1)

Trident D'Gao
Trident D'Gao

Reputation: 19762

thank to Felix Kling's advice i wrote the following transformer in typescript

import { FileInfo, API, Options } from 'jscodeshift';
import { resolve, normalize, relative } from 'path';

export default function transformer(file: FileInfo, api: API, options: Options) {
    const { j } = api;
    return j(file.source)
        .find(j.ExpressionStatement, { expression: { callee: { name: 'define' } } })
        .replaceWith(({ node }) => {
            const { expression: defineCallExpression } = node;

            if (defineCallExpression.type !== 'CallExpression') return crash('No call to define function of AMD.');
            const [moduleLocationsArray, callback] = defineCallExpression.arguments;
            if (callback.type !== 'FunctionExpression') return;
            if (moduleLocationsArray.type !== 'ArrayExpression') return;

            const imports = moduleLocationsArray.elements.map((element, index) => {
                if (element === null) return crash('Module name skipped.');
                if (element.type !== 'Literal') return crash('Module name is not a literal');
                const param = callback.params[index];
                if (param.type !== 'Identifier') return crash('Module parameter is not an identifier.');
                return {
                    location: element.value as string,
                    name: param.name,
                };
            }).filter(pair => shouldKeepModule(pair.location));

            const filePath = normalize(resolve(__dirname, file.path));
            const baseDir = normalize(resolve(__dirname, options.where));
            const importStatements = imports.map(({name, location}) => {
                const modulePath = normalize(resolve(baseDir, location));
                const relativeModuleName = slashings(relative(filePath, modulePath));
                const text = `const ${name} = require('${relativeModuleName}');`;
                const statement = api.j(text, options);
                return statement;
            });
            const statementsBefore = callback.body.body;
            const statementsAfter = [...importStatements, ...statementsBefore];
            return statementsAfter;
        })
        .toSource();
}

function shouldKeepModule(location: string): boolean {
    return location !== 'module' && location !== 'exports' && location !== 'require';
}
function crash(message: string): never { throw new Error(message); }
function slashings(text: string): string { return text.replace(/\\/g, '/'); }

with the following tsconfig.json

{
    "compileOnSave": true,
    "compilerOptions": {
        "strict": true,
        "target": "es6",
        "module": "commonjs",
        "lib": ["es6"],
        "types": ["node", "jscodeshift"],
        "outDir": "../../node_modules/amd-to-commonjs"
    }
}

with the following package.json

{
    "private": true,
    "devDependencies": {
        "@types/node": "7.0.4",
        "@types/jscodeshift": "0.6.0",
        "jscodeshift": "0.6.3",
        "typescript": "3.4.0-dev.20190227"
    }
}

built by the following command

npm install
node ../../node_modules/typescript/bin/tsc --project ./

and run by the following command

node ../../node_modules/jscodeshift/bin/jscodeshift.js --transform=../../node_modules/amd-to-commonjs/transformer.js --where=../../scripts/built ../../scripts/built

Upvotes: 1

Related Questions