Skip to content

Commit

Permalink
Merge pull request #17 from homer0/next
Browse files Browse the repository at this point in the history
Next (v3.0.0)
  • Loading branch information
homer0 authored May 17, 2022
2 parents 1d9a1fd + d49361d commit f504bee
Show file tree
Hide file tree
Showing 14 changed files with 3,769 additions and 3,478 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [ '12', '14' ]
node: [ '14', '16' ]
name: Run jest and ESLint (Node ${{ matrix.node }})
steps:
- uses: actions/checkout@v2
Expand All @@ -18,7 +18,7 @@ jobs:
- run: yarn lint:all
- run: yarn test
- name: Coveralls
if: ${{ matrix.node == '12' }}
if: ${{ matrix.node == '14' }}
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
12
14
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ module.exports = {
addModuleEntry: false,
addPackageJson: true,
filesWithShebang: [],
codemod: {
path: '',
files: ['cjs', 'exports', 'named-export-generation'],
},
};
```

Expand Down Expand Up @@ -186,6 +190,55 @@ For example, this project uses `src/bin.js`.

> Default `[]`
#### .codemod

Due to the `jscodeshift` and `5to6-codemod` projects not being updated quite often, it's not hard to run on scenarios in which your code is not compatible with the transformations, so this group of settings will allow you to run custom versions of the codemod, change the order fo the transformations, and even are your own.

##### .path

This is the path, relative to the working directory, in which the transformation files are located.

> Default `''` // On runtime, it gets resolved to `5to6-codemod/transforms`
##### .files

These are the name of the files for the transformations, inside the `path` directory.

The list can also be used to change the order of the default transformations, and it can also contain the `<cjs2esm>` special keyword, which references the tranformation file this package uses.

For example:

```json
{
"files": [
"cjs",
"<cjs2esm>",
"named-export-generation",
]
}
```

With that, `exports` wouldn't be used, and the package transformation would run before `named-export-generation`.

Local transformation files can also be specified, using path relatives to the working directory:

```json
{
"files": [
"cjs",
"<cjs2esm>",
"./my-custom-transformation",
"named-export-generation",
]
}
```

- ⚠️ If the list is empty, it will use the default value.
- ⚠️ The `<cjs2esm>` cannot be used as the first item in the list.
- ⚠️ The names can't contain the extension, and they need to be `.js` files.

> Default `['cjs', 'exports', 'named-export-generation']`
## ES Modules

Yes, if you want to use the tool as a library, the tool uses itself to generate a ESM version, so you can use the `/esm` path to access it:
Expand Down Expand Up @@ -270,3 +323,4 @@ Enjoy 🤘!

> ~~Once `v14` becomes the oldest LTS, I'll archive this repository and deprecate the tool.~~
> Node 12 now supports ESM without a flag, but there are still a lot of things that use CommonJS, and the fact that you can't `require` ESM makes things complicated, so I'm not sure yet when I'll deprecate the tool.
> Update: 2022, and the interop is still a mess, so I'm not sure when I'll deprecate the tool.
44 changes: 23 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,43 @@
],
"license": "MIT",
"dependencies": {
"@babel/core": "^7.15.8",
"@babel/preset-env": "^7.15.8",
"@babel/core": "^7.17.12",
"@babel/preset-env": "^7.17.12",
"5to6-codemod": "1.8.0",
"chalk": "^4.1.2",
"del": "^6.0.0",
"fs-extra": "^10.0.0",
"jscodeshift": "0.10.0"
"fs-extra": "^10.1.0",
"jscodeshift": "0.13.1",
"patch-package": "^6.4.7"
},
"devDependencies": {
"@commitlint/cli": "^13.2.1",
"@commitlint/config-conventional": "^13.2.0",
"@homer0/eslint-plugin": "^8.0.2",
"@commitlint/cli": "^17.0.0",
"@commitlint/config-conventional": "^17.0.0",
"@homer0/eslint-plugin": "^9.0.2",
"@homer0/prettier-config": "^1.1.1",
"@homer0/prettier-plugin-jsdoc": "^4.0.6",
"@semantic-release/changelog": "^5.0.1",
"@semantic-release/git": "^9.0.1",
"eslint": "^8.0.1",
"husky": "^7.0.2",
"is-ci": "^3.0.0",
"jest": "^27.2.5",
"@homer0/prettier-plugin-jsdoc": "^5.1.1",
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/git": "^10.0.1",
"eslint": "^8.15.0",
"husky": "^8.0.1",
"is-ci": "^3.0.1",
"jest": "^28.1.0",
"jimple": "^1.5.0",
"jsdoc": "^3.6.7",
"jsdoc": "^3.6.10",
"jsdoc-ts-utils": "^2.0.1",
"docdash": "homer0/docdash#semver:^2.1.0",
"leasot": "^12.0.0",
"lint-staged": "^11.2.3",
"prettier": "^2.4.1",
"semantic-release": "^17.4.7"
"leasot": "^13.1.0",
"lint-staged": "^12.4.1",
"prettier": "^2.6.2",
"semantic-release": "^19.0.2"
},
"main": "src/index.js",
"bin": {
"cjs2esm": "./src/bin.js"
},
"engine-strict": true,
"engines": {
"node": ">=12"
"node": ">=14"
},
"commitlint": {
"extends": [
Expand All @@ -69,7 +70,8 @@
"lint:all": "./utils/scripts/lint-all",
"docs": "./utils/scripts/docs",
"todo": "./utils/scripts/todo",
"prepare": "./utils/scripts/prepare"
"prepare": "./utils/scripts/prepare",
"postinstall": "./utils/scripts/postinstall"
},
"config": {
"cjs2esm": {
Expand Down
19 changes: 19 additions & 0 deletions patches/5to6-codemod+1.8.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
diff --git a/node_modules/5to6-codemod/transforms/exports.js b/node_modules/5to6-codemod/transforms/exports.js
index 55f2a19..fe4fd22 100644
--- a/node_modules/5to6-codemod/transforms/exports.js
+++ b/node_modules/5to6-codemod/transforms/exports.js
@@ -44,10 +44,10 @@ module.exports = function(file, api, options) {
})
filteredPaths.forEach(function (p, i) {
// aggregate all specifiers
- specifiers.push(j.exportSpecifier(
- j.identifier(p.value.right.name),
- j.identifier(p.value.right.name)
- ))
+ specifiers.push(j.exportSpecifier.from({
+ exported: j.identifier(p.value.right.name),
+ local: j.identifier(p.value.right.name),
+ }));

// replace the last module.exports.*
if (i === filteredPaths.length - 1) {
75 changes: 63 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ const fs = require('fs-extra');
const Runner = require('jscodeshift/src/Runner');
const { repository } = require('../package.json');
const { log, findFile, getAbsPathInfo, requireModule } = require('./utils');

/**
* The name that can be used in the list of files to specify the custom transformation
* this module does.
*
* @type {string}
*/
const CJS2ESM_TRANSFORMATION_NAME = '<cjs2esm>';
/**
* This is called every time an unexpected error is thrown; it logs the error using the
* `log`
Expand Down Expand Up @@ -229,8 +237,8 @@ const copyFiles = async (input, output, useExtension, forceDirectory) => {
* process explodes when a file has one.
*
* @param {CJS2ESMCopiedFile[]} files The list of copied files with shebangs.
* @returns {Object.<string, string>} The keys are the path to the copied files and the
* values the shebangs they had.
* @returns {Promise<Object.<string, string>>} The keys are the path to the copied files
* and the values the shebangs they had.
* @ignore
*/
const removeShebangs = async (files) => {
Expand Down Expand Up @@ -303,7 +311,7 @@ const transformOutput = async (files, options) => {
cjs2esm: options,
};

const shebangExpressions = options.filesWithShebang.map(
const shebangExpressions = (options.filesWithShebang || []).map(
(expression) => new RegExp(expression),
);
const filesWithShebang = files.filter(({ from }) =>
Expand All @@ -315,13 +323,56 @@ const transformOutput = async (files, options) => {
shebangs = await removeShebangs(filesWithShebang);
}

const fiveToSixCodeModPath = path.resolve('node_modules', '5to6-codemod', 'transforms');
const transformations = [
path.join(fiveToSixCodeModPath, 'cjs.js'),
path.join(fiveToSixCodeModPath, 'exports.js'),
path.join(fiveToSixCodeModPath, 'named-export-generation.js'),
path.join(__dirname, 'transformer.js'),
];
const codemodOptions = {
path: null,
files: null,
...options.codemod,
};

const codemodFiles =
Array.isArray(codemodOptions.files) && codemodOptions.files.length
? codemodOptions.files
: ['cjs', 'exports', 'named-export-generation'];
if (!codemodFiles.includes(CJS2ESM_TRANSFORMATION_NAME)) {
codemodFiles.push(CJS2ESM_TRANSFORMATION_NAME);
}

const fileForResolveIndex = codemodFiles.findIndex(
(file) => file !== CJS2ESM_TRANSFORMATION_NAME && file.match(/^\w/),
);

if (codemodFiles[0] === CJS2ESM_TRANSFORMATION_NAME) {
throw new Error(`${CJS2ESM_TRANSFORMATION_NAME} cannot be the first one in the list`);
}

const fileForResolve = `${codemodFiles[fileForResolveIndex]}.js`;

const cwd = process.cwd();
let filepathForResolve;
if (codemodOptions.path) {
filepathForResolve = path.join(cwd, codemodOptions.path, fileForResolve);
} else {
filepathForResolve = require.resolve(
path.join('5to6-codemod', 'transforms', fileForResolve),
);
}

const codeModPath = path.dirname(filepathForResolve);
const transformations = codemodFiles.map((file, index) => {
if (index === fileForResolveIndex) {
return filepathForResolve;
}
if (file === CJS2ESM_TRANSFORMATION_NAME) {
return path.join(__dirname, 'transformer.js');
}

const fileWithExt = `${file}.js`;
if (fileWithExt.startsWith('.')) {
return path.resolve(fileWithExt);
}

return path.join(codeModPath, `${file}.js`);
});

log('yellow', `Transforming ${files.length} files...`);

Expand Down Expand Up @@ -352,7 +403,6 @@ const transformOutput = async (files, options) => {
);
}

const cwd = process.cwd();
files.forEach((file) => log('gray', `> ${file.to.substr(cwd.length + 1)}`));
let totalTime = results.reduce(
(acc, { timeElapsed }) => acc + parseFloat(timeElapsed),
Expand All @@ -368,7 +418,7 @@ const transformOutput = async (files, options) => {
* will check for `index.mjs` and `index.js`.
*
* @param {string} absPath The absolute path to the folder.
* @returns {?string} If there's no `index`, the function will return `null`.
* @returns {Promise<?string>} If there's no `index`, the function will return `null`.
* @ignore
*/
const findFolderEntryPath = async (absPath) => {
Expand Down Expand Up @@ -444,3 +494,4 @@ module.exports.copyFiles = copyFiles;
module.exports.transformOutput = transformOutput;
module.exports.updatePackageJSON = updatePackageJSON;
module.exports.addPackageJSON = addPackageJSON;
module.exports.CJS2ESM_TRANSFORMATION_NAME = CJS2ESM_TRANSFORMATION_NAME;
21 changes: 19 additions & 2 deletions src/transformer.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* Some comment about the function.
*/
const path = require('path');
const fs = require('fs-extra');
const { findFileSync, getAbsPathInfoSync } = require('./utils');
Expand Down Expand Up @@ -54,6 +57,8 @@ const transform = (file, api, options) => {
// Generate the list of expressions to ignore import statements.
const ignoreListForExt = cjs2esm.extension.ignore.map((ignore) => new RegExp(ignore));

const originalFirstNode = root.find(j.Program).get('body', 0).node;

// =================================================
// Parse the import statements to add missing extensions.
// =================================================
Expand Down Expand Up @@ -117,13 +122,25 @@ const transform = (file, api, options) => {
.replaceWith((item) => {
const importPath = item.value.source.value;
const info = cjs2esm.modules.find((mod) => importPath.startsWith(mod.name));
const find = info.find ? new RegExp(info.find) : new RegExp(`^${info.name}`);
const replacement = importPath.replace(find, info.path);
let replacement;
if (info.find) {
replacement = importPath.replace(new RegExp(info.find), info.path);
} else {
replacement = importPath.replace(
new RegExp(`^${info.name}($|\\/)`),
(match, endChar) => (endChar.endsWith('/') ? `${info.path}/` : info.path),
);
}

return j.importDeclaration(item.value.specifiers, j.literal(replacement));
});
}

const newFirstNode = root.find(j.Program).get('body', 0).node;
if (newFirstNode !== originalFirstNode) {
newFirstNode.comments = originalFirstNode.comments;
}

// Regenerate the file code.
return root.toSource({
quote: 'single',
Expand Down
13 changes: 13 additions & 0 deletions src/typedef.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
* when validating the use of extensions.
*/

/**
* @typedef {Object} CJS2CodemodOptions
* @property {?string} path The path, relative to the cwd, to the transformations
* directory. By default, `5to6-codemod`, relative to this
* module.
* @property {?string[]} files The list of transformations to use, without extension, as
* `.js` will be used`. If not defined, empty, or not an
* array, it will use the defaults: `cjs`, `exports` and
* `named-export-generation`.
*/

/**
* @typedef {Object} CJS2ESMOptions
* @property {string[]} input
Expand Down Expand Up @@ -47,6 +58,8 @@
* transforming them in order to avoid issues with the parsers. The list are strings that
* will be converted on into `RegExp`s, so they can be a parts of the path, or
* expressions.
* @property {?CJS2ESMOptions} codemod
* Options to customize integration with the codemod tool, for the transformations.
*/

/**
Expand Down
4 changes: 2 additions & 2 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const parseJSPath = (filepath) => {
* Tries to find the extension for a file import path.
*
* @param {string} absPath The generated absolute path for the file.
* @returns {?string}
* @returns {Promise<?string>}
* @ignore
*/
const findFileExtension = async (absPath) => {
Expand Down Expand Up @@ -120,7 +120,7 @@ const findFileExtensionSync = (absPath) => {
* case it's missing.
*
* @param {string} absPath The absolute path for the resource.
* @returns {?AbsPathInfo}
* @returns {Promise<?AbsPathInfo>}
*/
const getAbsPathInfo = async (absPath) => {
const info = parseJSPath(absPath);
Expand Down
Loading

0 comments on commit f504bee

Please sign in to comment.