Skip to content

Commit

Permalink
feat: convert to defineAsyncComponent (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
wattanx authored May 11, 2024
1 parent 1ab6a17 commit 6441081
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 51 deletions.
3 changes: 2 additions & 1 deletion packages/vue-script-setup-converter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
},
"dependencies": {
"@vue/compiler-sfc": "^3.2.40",
"@wattanx/converter-utils": "*"
"@wattanx/converter-utils": "*",
"knitwork": "^1.1.0"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`snapshot > defineNuxtComponent 1`] = `
"import { useNuxtApp } from '#imports';
"import { useNuxtApp } from "#imports";
definePageMeta({
name: 'HelloWorld', layout: 'test-layout', middleware: 'test-middleware'
});
Expand All @@ -15,7 +15,7 @@ const onSubmit = () => {
`;

exports[`snapshot > lang=js 1`] = `
"import { toRefs, computed, ref } from 'vue';
"import { toRefs, computed, ref } from "vue";
const props = defineProps({
msg: {
type: String,
Expand All @@ -35,7 +35,7 @@ const count = ref(0);
`;

exports[`snapshot > lang=ts 1`] = `
"import { toRefs, computed, ref } from 'vue';
"import { toRefs, computed, ref } from "vue";
type Props = {
msg?: string;
foo: string;
Expand Down
28 changes: 25 additions & 3 deletions packages/vue-script-setup-converter/src/lib/convertSrc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default defineComponent({
</script>`);
expect(output).toMatchInlineSnapshot(
`
"import { toRefs, computed } from 'vue';
"import { toRefs, computed } from "vue";
type Props = { msg?: string; }; const props = withDefaults(defineProps<Props>(), { msg: 'HelloWorld' });
const { msg } = toRefs(props);
Expand Down Expand Up @@ -156,13 +156,35 @@ export default defineComponent({
</script>`);
expect(output).toMatchInlineSnapshot(
`
"import type { PropType } from 'vue';
import { computed } from 'vue';
"import { computed } from "vue";
import type { PropType } from 'vue';
type Props = { msg?: string; }; const props = withDefaults(defineProps<Props>(), { msg: 'HelloWorld' });
const newMsg = computed(() => props.msg + '- HelloWorld');
"
`
);
});

it("should be converted to defineAsyncComponent", () => {
const output = convertSrc(`<script>
import { defineComponent } from 'vue';
import HelloWorld from './HelloWorld.vue';
export default defineComponent({
components: {
HelloWorld,
MyComp: () => import('./MyComp.vue'),
Foo: () => import('./Foo.vue'),
}
})
</script>`);
expect(output).toMatchInlineSnapshot(`
"import { defineAsyncComponent } from "vue";
import HelloWorld from './HelloWorld.vue';
const MyComp = defineAsyncComponent(() => import('./MyComp.vue'));
const Foo = defineAsyncComponent(() => import('./Foo.vue'));
"
`);
});
});
25 changes: 22 additions & 3 deletions packages/vue-script-setup-converter/src/lib/convertSrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { convertPageMeta } from "./converter/pageMetaConverter";
import { convertProps } from "./converter/propsConverter";
import { convertSetup } from "./converter/setupConverter";
import { convertEmits } from "./converter/emitsConverter";
import { convertComponents } from "./converter/componentsConverter";
import { genImport } from "knitwork";

export const convertSrc = (input: string) => {
const {
Expand Down Expand Up @@ -43,14 +45,31 @@ export const convertSrc = (input: string) => {
throw new Error("defineComponent is not found.");
}

const importDeclaration = convertImportDeclaration(sourceFile) ?? "";
const importMap = convertImportDeclaration(sourceFile) ?? "";
const pageMeta = convertPageMeta(callexpression, lang) ?? "";
const props = convertProps(callexpression, lang) ?? "";
const emits = convertEmits(callexpression, lang) ?? "";
const statement = convertSetup(callexpression) ?? "";
const components = convertComponents(callexpression) ?? "";

const hasDynamicImport = components.includes("defineAsyncComponent");

const statements = project.createSourceFile("new.tsx");

if (
hasDynamicImport &&
!importMap[0].importSpecifiers.includes("defineAsyncComponent")
) {
importMap[0].importSpecifiers.push("defineAsyncComponent");
statements.addStatements(
importMap.map((x) => genImport(x.moduleSpecifier, x.importSpecifiers))
);
} else {
statements.addStatements(
importMap.map((x) => genImport(x.moduleSpecifier, x.importSpecifiers))
);
}

statements.addStatements(
sourceFile
.getStatements()
Expand All @@ -70,12 +89,12 @@ export const convertSrc = (input: string) => {
})
);

statements.addStatements(importDeclaration);

if (isDefineNuxtComponent(callexpression)) {
statements.addStatements(pageMeta);
}

statements.addStatements(components);

statements.addStatements(props);
statements.addStatements(emits);
statements.addStatements(statement);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { expect, test } from "vitest";
import type { CallExpression } from "ts-morph";
import { ScriptTarget, SyntaxKind, Project } from "ts-morph";
import { parse } from "@vue/compiler-sfc";
import { getNodeByKind } from "../helpers/node";
import { convertComponents } from "./componentsConverter";

const parseScript = (input: string, lang: "js" | "ts" = "js") => {
const {
descriptor: { script },
} = parse(input);

const project = new Project({
tsConfigFilePath: "tsconfig.json",
compilerOptions: {
target: ScriptTarget.Latest,
},
});

const sourceFile = project.createSourceFile("s.tsx", script?.content ?? "");

const callexpression = getNodeByKind(sourceFile, SyntaxKind.CallExpression);

const components = convertComponents(callexpression as CallExpression);

return components;
};

test("should be converted to defineAsyncComponent", () => {
const source = `<script>
import { defineComponent } from 'vue';
import HelloWorld from './HelloWorld.vue';
export default defineComponent({
components: {
HelloWorld,
MyComp: () => import('./MyComp.vue'),
Foo: () => import('./Foo.vue'),
}
})
`;
const output = parseScript(source);

expect(output).toMatchInlineSnapshot(
`
"const MyComp = defineAsyncComponent(() => import('./MyComp.vue'))
const Foo = defineAsyncComponent(() => import('./Foo.vue'))"
`
);
});

test("should be output as is", () => {
const source = `<script>
import { defineComponent, defineAsyncComponent } from 'vue';
import HelloWorld from './HelloWorld.vue';
export default defineComponent({
components: {
HelloWorld,
MyComp: defineAsyncComponent(() => import('./MyComp.vue')),
Foo: defineAsyncComponent(() => import('./Foo.vue')),
}
})
`;
const output = parseScript(source);

expect(output).toMatchInlineSnapshot(`
"const MyComp = defineAsyncComponent(() => import('./MyComp.vue'))
const Foo = defineAsyncComponent(() => import('./Foo.vue'))"
`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { CallExpression, PropertyAssignment } from "ts-morph";
import { Node, SyntaxKind } from "ts-morph";
import { getOptionsNode } from "../helpers/node";
import { filterDynamicImport } from "../helpers/module";

export const convertComponents = (node: CallExpression) => {
const componentsNode = getOptionsNode(node, "components");

if (!componentsNode) {
return "";
}

return convertToDefineAsyncComponent(componentsNode);
};

const convertToDefineAsyncComponent = (node: PropertyAssignment) => {
const child = node.getInitializer();

if (!child) {
throw new Error("components is empty.");
}

if (!Node.isObjectLiteralExpression(child)) return "";

const properties = child.getProperties();

const filterdProperties = properties.filter(filterDynamicImport);

if (filterdProperties.length === 0) return "";

const value = filterdProperties
.map((x) => {
const propertyName = x.getName();
const initializer = x.getInitializer();

if (!initializer) return "";

if (initializer.getText().includes("defineAsyncComponent")) {
return `const ${propertyName} = ${initializer.getText()}`;
}

const arrowFunc = x.getFirstChildByKind(SyntaxKind.ArrowFunction);
return `const ${propertyName} = defineAsyncComponent(${arrowFunc!.getText()})`;
})
.join("\n");

return value;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ const parseScript = (input: string) => {
});

const sourceFile = project.createSourceFile("s.tsx", script?.content ?? "");
const convertedImportDeclarationText = convertImportDeclaration(sourceFile);

return convertedImportDeclarationText;
return convertImportDeclaration(sourceFile);
};

describe("convertImportDeclaration", () => {
Expand All @@ -34,7 +32,12 @@ describe("convertImportDeclaration", () => {
it("returns import declaration text removed defineComponent", () => {
const output = parseScript(source);

expect(output).toMatchInlineSnapshot(`"import { ref } from 'vue';"`);
expect(output).toEqual([
{
importSpecifiers: ["ref"],
moduleSpecifier: "vue",
},
]);
});
});

Expand All @@ -47,10 +50,12 @@ describe("convertImportDeclaration", () => {
})
</script>`;

it("returns blank", () => {
it("importSpecifiers returns blank", () => {
const output = parseScript(source);

expect(output).toBe("");
expect(output).toEqual([
{ importSpecifiers: [], moduleSpecifier: "vue" },
]);
});
});

Expand All @@ -66,7 +71,12 @@ describe("convertImportDeclaration", () => {
it("returns import declaration text removed defineNuxtComponent", () => {
const output = parseScript(source);

expect(output).toMatchInlineSnapshot(`"import { ref } from '#imports';"`);
expect(output).toEqual([
{
importSpecifiers: ["ref"],
moduleSpecifier: "#imports",
},
]);
});
});

Expand All @@ -79,10 +89,15 @@ describe("convertImportDeclaration", () => {
})
</script>`;

it("returns blank", () => {
it("importSpecifiers returns blank", () => {
const output = parseScript(source);

expect(output).toBe("");
expect(output).toEqual([
{
importSpecifiers: [],
moduleSpecifier: "#imports",
},
]);
});
});
});
Loading

0 comments on commit 6441081

Please sign in to comment.