mirror of
https://github.com/google/blockly.git
synced 2026-01-05 08:00:09 +01:00
feat(scripts): Create script to help with CJS -> ESM migration (#8197)
* feat(scripts): Create script to help with CJS -> ESM migration
This is based on js2ts, but considerably simplified since we
no longer need to deal with goog.module IDs.
* feat(scripts): Add support for module.exports = {...}
Support (optionally renaming) assignments to module.exports, in
addition to the existing support for simple exports property
assignmenets.
* fix(scripts): Typo corrections + other improvments for PR #8197
* chore(scripts): Format
This commit is contained in:
committed by
GitHub
parent
dc91c3ab54
commit
5a9a31ad1d
162
scripts/migration/cjs2esm
Executable file
162
scripts/migration/cjs2esm
Executable file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const filenames = process.argv.slice(2); // Trim off node and script name.
|
||||
|
||||
/** Absolute path of repository root. */
|
||||
const repoPath = path.resolve(__dirname, '..', '..');
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Process files mentioned on the command line.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** RegExp matching require statements. */
|
||||
const requireRE =
|
||||
/(?:const\s+(?:([$\w]+)|(\{[^}]*\}))\s*=\s*)?require\('([^']+)'\);/g;
|
||||
|
||||
/** RegExp matching key: value pairs in destructuring assignments. */
|
||||
const keyValueRE = /([$\w]+)\s*:\s*([$\w]+)\s*(?=,|})/g;
|
||||
|
||||
/** Prefix for RegExp matching a top-level declaration. */
|
||||
const declPrefix = '(?:const|let|var|(?:async\\s+)?function(?:\\s*\\*)?|class)';
|
||||
|
||||
for (const filename of filenames) {
|
||||
let contents = null;
|
||||
try {
|
||||
contents = String(fs.readFileSync(filename));
|
||||
} catch (e) {
|
||||
console.error(`error while reading ${filename}: ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
console.log(`Converting ${filename} from CJS to ESM...`);
|
||||
|
||||
// Remove "use strict".
|
||||
contents = contents.replace(/^\s*["']use strict["']\s*; *\n/m, '');
|
||||
|
||||
// Migrate from require to import.
|
||||
contents = contents.replace(
|
||||
requireRE,
|
||||
function (
|
||||
orig, // Whole statement to be replaced.
|
||||
name, // Name of named import of whole module (if applicable).
|
||||
names, // {}-enclosed list of destructured imports.
|
||||
moduleName, // Imported module name or path.
|
||||
) {
|
||||
if (moduleName[0] === '.') {
|
||||
// Relative path. Could check and add '.mjs' suffix if desired.
|
||||
}
|
||||
if (name) {
|
||||
return `import * as ${name} from '${moduleName}';`;
|
||||
} else if (names) {
|
||||
names = names.replace(keyValueRE, '$1 as $2');
|
||||
return `import ${names} from '${moduleName}';`;
|
||||
} else {
|
||||
// Side-effect only require.
|
||||
return `import '${moduleName}';`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Find and update or remove old-style single-export assignments
|
||||
// like:
|
||||
//
|
||||
// exports.bar = foo; // becomes export {foo as bar};
|
||||
// exports.foo = foo; // remove the export and export at declaration
|
||||
// // instead, if possible.
|
||||
/** @type {!Array<{name: string, re: RegExp>}>} */
|
||||
const easyExports = [];
|
||||
contents = contents.replace(
|
||||
/^\s*exports\.([$\w]+)\s*=\s*([$\w]+)\s*;\n/gm,
|
||||
function (
|
||||
orig, // Whole statement to be replaced.
|
||||
exportName, // Name to export item as.
|
||||
declName, // Already-declared name for item being exported.
|
||||
) {
|
||||
// Renamed exports have to be translated as-is.
|
||||
if (exportName !== declName) {
|
||||
return `export {${declName} as ${exportName}};\n`;
|
||||
}
|
||||
// OK, we're doing "export.foo = foo;". Can we update the
|
||||
// declaration? We can't actualy modify it yet as we're in
|
||||
// the middle of a search-and-replace on contents already, but
|
||||
// we can delete the old export and later update the
|
||||
// declaration into an export.
|
||||
const declRE = new RegExp(
|
||||
`^(\\s*)(${declPrefix}\\s+${declName})\\b`,
|
||||
'gm',
|
||||
);
|
||||
if (contents.match(declRE)) {
|
||||
easyExports.push({exportName, declRE});
|
||||
return ''; // Delete existing export assignment.
|
||||
} else {
|
||||
return `export ${exportName};\n`; // Safe fallback.
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Find and update or remove old-style module.exports assignment
|
||||
// like:
|
||||
//
|
||||
// module.exports = {foo, bar: baz, quux};
|
||||
//
|
||||
// which becomes export {baz as bar}, with foo and quux exported at
|
||||
// declaration instead, if possible.
|
||||
contents = contents.replace(
|
||||
/^module\.exports\s*=\s*\{([^\}]+)\};?(\n?)/m,
|
||||
function (
|
||||
orig, // Whole statement to be replaced.
|
||||
items, // List of items to be exported.
|
||||
) {
|
||||
items = items.replace(
|
||||
/( *)([$\w]+)\s*(?::\s*([$\w]+)\s*)?,?(\s*?\n?)/gm,
|
||||
function (
|
||||
origItem, // Whole item being replaced.
|
||||
indent, // Optional leading whitespace.
|
||||
exportName, // Name to export item as.
|
||||
declName, // Already-declared name being exported, if different.
|
||||
newline, // Optional trailing whitespace.
|
||||
) {
|
||||
if (!declName) declName = exportName;
|
||||
|
||||
// Renamed exports have to be translated as-is.
|
||||
if (exportName !== declName) {
|
||||
return `${indent}${declName} as ${exportName},${newline}`;
|
||||
}
|
||||
// OK, this item has no rename. Can we update the
|
||||
// declaration? We can't actualy modify it yet as we're in
|
||||
// the middle of a search-and-replace on contents already,
|
||||
// but we can delete the item and later update the
|
||||
// declaration into an export.
|
||||
const declRE = new RegExp(
|
||||
`^(\\s*)(${declPrefix}\\s+${declName})\\b`,
|
||||
'gm',
|
||||
);
|
||||
if (contents.match(declRE)) {
|
||||
easyExports.push({exportName, declRE});
|
||||
return ''; // Delete existing item.
|
||||
} else {
|
||||
return `${indent}${exportName},${newline}`; // Safe fallback.
|
||||
}
|
||||
},
|
||||
);
|
||||
if (/^\s*$/s.test(items)) {
|
||||
// No items left?
|
||||
return ''; // Delete entire module.export assignment.
|
||||
} else {
|
||||
return `export {${items}};\n`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Add 'export' to existing declarations where appropriate.
|
||||
for (const {exportName, declRE} of easyExports) {
|
||||
contents = contents.replace(declRE, '$1export $2');
|
||||
}
|
||||
|
||||
// Write converted file with new extension.
|
||||
const newFilename = filename.replace(/.c?js$/, '.mjs');
|
||||
fs.writeFileSync(newFilename, contents);
|
||||
console.log(`Wrote ${newFilename}.`);
|
||||
}
|
||||
Reference in New Issue
Block a user