Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: convert to defineAsyncComponent #56

Merged
merged 8 commits into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading