refactor(remark-plugin-npm2yarn): migrate package to TS (#5931)

* refactor(remark-plugin-npm2yarn): migrate package to TS

* fix(remark-plugin-npm2yarn): type as unified Plugin

* refactor(remark-plugin-npm2yarn): standardize code style with remark plugins in mdx-loader

* Use unist-util-visit

* Use export =

* Remove unneeded includes option

* Fix tests

* Migrate test to TS

* Make output look better

Co-authored-by: Josh-Cena <sidachen2003@gmail.com>
This commit is contained in:
duanwilliam 2021-11-12 05:21:16 -08:00 committed by GitHub
parent c914da3a0c
commit 1e725a158e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 116 deletions

View file

@ -2,10 +2,14 @@
"name": "@docusaurus/remark-plugin-npm2yarn",
"version": "2.0.0-beta.9",
"description": "Remark plugin for converting npm commands to Yarn commands as tabs.",
"main": "src/index.js",
"main": "lib/index.js",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
},
"repository": {
"type": "git",
"url": "https://github.com/facebook/docusaurus.git",
@ -13,9 +17,11 @@
},
"license": "MIT",
"dependencies": {
"npm-to-yarn": "^1.0.1"
"npm-to-yarn": "^1.0.1",
"unist-util-visit": "^2.0.2"
},
"devDependencies": {
"@types/mdast": "^3.0.10",
"remark": "^12.0.0",
"remark-mdx": "^1.6.21",
"to-vfile": "^6.0.0"

View file

@ -5,11 +5,7 @@ exports[`npm2yarn plugin test: already imported tabs components above are not re
import TabItem from '@theme/TabItem';
<Tabs defaultValue=\\"npm\\" values={[
{ label: 'npm', value: 'npm', },
{ label: 'Yarn', value: 'yarn', },
]}
>
<Tabs>
<TabItem value=\\"npm\\">
\`\`\`bash
@ -17,7 +13,7 @@ import TabItem from '@theme/TabItem';
\`\`\`
</TabItem>
<TabItem value=\\"yarn\\">
<TabItem value=\\"yarn\\" label=\\"Yarn\\">
\`\`\`bash
$ yarn add --global docusaurus
@ -29,11 +25,7 @@ import TabItem from '@theme/TabItem';
`;
exports[`npm2yarn plugin test: already imported tabs components below are not re-imported 1`] = `
"<Tabs defaultValue=\\"npm\\" values={[
{ label: 'npm', value: 'npm', },
{ label: 'Yarn', value: 'yarn', },
]}
>
"<Tabs>
<TabItem value=\\"npm\\">
\`\`\`bash
@ -41,7 +33,7 @@ exports[`npm2yarn plugin test: already imported tabs components below are not re
\`\`\`
</TabItem>
<TabItem value=\\"yarn\\">
<TabItem value=\\"yarn\\" label=\\"Yarn\\">
\`\`\`bash
$ yarn add --global docusaurus
@ -60,11 +52,7 @@ exports[`npm2yarn plugin test: installation file 1`] = `
"import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
<Tabs defaultValue=\\"npm\\" values={[
{ label: 'npm', value: 'npm', },
{ label: 'Yarn', value: 'yarn', },
]}
>
<Tabs>
<TabItem value=\\"npm\\">
\`\`\`bash
@ -72,7 +60,7 @@ import TabItem from '@theme/TabItem';
\`\`\`
</TabItem>
<TabItem value=\\"yarn\\">
<TabItem value=\\"yarn\\" label=\\"Yarn\\">
\`\`\`bash
$ yarn add --global docusaurus
@ -106,11 +94,7 @@ import TabItem from '@theme/TabItem';
A plugin is usually a npm package, so you install them like other npm packages using npm.
<Tabs defaultValue=\\"npm\\" values={[
{ label: 'npm', value: 'npm', },
{ label: 'Yarn', value: 'yarn', },
]}
>
<Tabs>
<TabItem value=\\"npm\\">
\`\`\`bash
@ -118,7 +102,7 @@ npm install --save docusaurus-plugin-name
\`\`\`
</TabItem>
<TabItem value=\\"yarn\\">
<TabItem value=\\"yarn\\" label=\\"Yarn\\">
\`\`\`bash
yarn add docusaurus-plugin-name

View file

@ -6,14 +6,19 @@
*/
import remark from 'remark';
import npm2yarn from '../index';
// import from the transpiled lib because Babel can't transpile `export =` syntax
// TODO change to `../index` after migrating to ESM
import npm2yarn from '../../lib/index';
import vfile from 'to-vfile';
import {join, relative} from 'path';
import mdx from 'remark-mdx';
const staticDir = `./${relative(process.cwd(), join(__dirname, 'fixtures'))}`;
const processFixture = async (name, options) => {
const processFixture = async (
name: string,
options: {sync?: boolean; staticDir: string},
) => {
const path = join(__dirname, 'fixtures', `${name}.md`);
const file = await vfile.read(path);
const result = await remark()

View file

@ -1,87 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const npmToYarn = require('npm-to-yarn');
// E.g. global install: 'npm i' -> 'yarn'
const convertNpmToYarn = (npmCode) => npmToYarn(npmCode, 'yarn');
const transformNode = (node, isSync) => {
const groupIdProp = isSync ? 'groupId="npm2yarn" ' : '';
const npmCode = node.value;
const yarnCode = convertNpmToYarn(node.value);
return [
{
type: 'jsx',
value:
`<Tabs defaultValue="npm" ${groupIdProp}` +
`values={[
{ label: 'npm', value: 'npm', },
{ label: 'Yarn', value: 'yarn', },
]}
>
<TabItem value="npm">`,
},
{
type: node.type,
lang: node.lang,
value: npmCode,
},
{
type: 'jsx',
value: '</TabItem>\n<TabItem value="yarn">',
},
{
type: node.type,
lang: node.lang,
value: yarnCode,
},
{
type: 'jsx',
value: '</TabItem>\n</Tabs>',
},
];
};
const matchNode = (node) => node.type === 'code' && node.meta === 'npm2yarn';
const nodeForImport = {
type: 'import',
value:
"import Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';",
};
module.exports = (options = {}) => {
const {sync = false} = options;
let transformed = false;
let alreadyImported = false;
const transformer = (node) => {
if (node.type === 'import' && node.value.includes('@theme/Tabs')) {
alreadyImported = true;
}
if (matchNode(node)) {
transformed = true;
return transformNode(node, sync);
}
if (Array.isArray(node.children)) {
let index = 0;
while (index < node.children.length) {
const result = transformer(node.children[index]);
if (result) {
node.children.splice(index, 1, ...result);
index += result.length;
} else {
index += 1;
}
}
}
if (node.type === 'root' && transformed && !alreadyImported) {
node.children.unshift(nodeForImport);
}
return null;
};
return transformer;
};

View file

@ -0,0 +1,95 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type {Code, Content, Literal} from 'mdast';
import type {Plugin, Transformer} from 'unified';
import type {Node, Parent} from 'unist';
import visit from 'unist-util-visit';
import npmToYarn from 'npm-to-yarn';
interface PluginOptions {
sync?: boolean;
}
// E.g. global install: 'npm i' -> 'yarn'
const convertNpmToYarn = (npmCode: string) => npmToYarn(npmCode, 'yarn');
const transformNode = (node: Code, isSync: boolean) => {
const groupIdProp = isSync ? ' groupId="npm2yarn"' : '';
const npmCode = node.value;
const yarnCode = convertNpmToYarn(node.value);
return [
{
type: 'jsx',
value: `<Tabs${groupIdProp}>\n<TabItem value="npm">`,
},
{
type: node.type,
lang: node.lang,
value: npmCode,
},
{
type: 'jsx',
value: '</TabItem>\n<TabItem value="yarn" label="Yarn">',
},
{
type: node.type,
lang: node.lang,
value: yarnCode,
},
{
type: 'jsx',
value: '</TabItem>\n</Tabs>',
},
] as Content[];
};
const isImport = (node: Node): node is Literal => node.type === 'import';
const isParent = (node: Node): node is Parent =>
Array.isArray((node as Parent).children);
const matchNode = (node: Node): node is Code =>
node.type === 'code' && (node as Code).meta === 'npm2yarn';
const nodeForImport: Literal = {
type: 'import',
value:
"import Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';",
};
const plugin: Plugin<[PluginOptions?]> = (options = {}) => {
const {sync = false} = options;
let transformed = false;
let alreadyImported = false;
const transformer: Transformer = (root) => {
visit(root, (node: Node) => {
if (isImport(node) && node.value.includes('@theme/Tabs')) {
alreadyImported = true;
}
if (isParent(node)) {
let index = 0;
while (index < node.children.length) {
const child = node.children[index];
if (matchNode(child)) {
const result = transformNode(child, sync);
node.children.splice(index, 1, ...result);
index += result.length;
transformed = true;
} else {
index += 1;
}
}
}
});
if (transformed && !alreadyImported) {
(root as Parent).children.unshift(nodeForImport);
}
};
return transformer;
};
// To continue supporting `require('npm2yarn')` without the `.default` ㄟ(▔,▔)ㄏ
// TODO change to export default after migrating to ESM
export = plugin;

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./lib/.tsbuildinfo",
"rootDir": "src",
"outDir": "lib"
}
}

View file

@ -4262,7 +4262,7 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.174.tgz#b4b06b6eced9850eed6b6a8f1abdd0f5192803c1"
integrity sha512-KMBLT6+g9qrGXpDt7ohjWPUD34WA/jasrtjTEHStF0NPdEwJ1N9SZ+4GaMVDeuk/y0+X5j9xFm6mNiXS7UoaLQ==
"@types/mdast@^3.0.0", "@types/mdast@^3.0.7":
"@types/mdast@^3.0.0", "@types/mdast@^3.0.10", "@types/mdast@^3.0.7":
version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af"
integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==