From 634b73c1e7645e22c2239534cc63d409f65d2c4f Mon Sep 17 00:00:00 2001 From: Bert De Block Date: Wed, 10 Dec 2025 16:10:24 +0100 Subject: [PATCH] Support generating named exports instead of default exports for component, helper, modifier and util generators --- src/generators/generator.ts | 10 ++++ src/generators/generators.ts | 8 +-- templates/component/component.class-based.gjs | 2 +- templates/component/component.class-based.gts | 2 +- .../component/component.template-only.gts | 4 +- templates/helper/helper.class-based.js | 2 +- templates/helper/helper.class-based.ts | 2 +- templates/helper/helper.function-based.js | 2 +- templates/modifier/modifier.class-based.js | 2 +- templates/modifier/modifier.class-based.ts | 2 +- templates/modifier/modifier.function-based.js | 2 +- templates/modifier/modifier.function-based.ts | 2 +- templates/util/util.js | 2 +- .../__snapshots__/component.test.ts.snap | 43 +++++++++++++++ .../__snapshots__/helper.test.ts.snap | 48 +++++++++++++++++ .../__snapshots__/modifier.test.ts.snap | 52 +++++++++++++++++++ .../__snapshots__/util.test.ts.snap | 14 +++++ test/generators/component.test.ts | 26 +++++++++- test/generators/helper.test.ts | 25 ++++++++- test/generators/modifier.test.ts | 27 +++++++++- test/generators/util.test.ts | 20 ++++++- 21 files changed, 279 insertions(+), 18 deletions(-) diff --git a/src/generators/generator.ts b/src/generators/generator.ts index 2d4d118..c8b19b8 100644 --- a/src/generators/generator.ts +++ b/src/generators/generator.ts @@ -154,6 +154,7 @@ export function defineGenerator({ }; const templateCompiled = template({ + args: resolvedArgs, name: { ...entityNameCases, camelCurlyBrackets: `{{${entityNameCases.camel}}}`, @@ -327,6 +328,15 @@ export function name(): GeneratorArgFactory { }); } +export function namedExport(): GeneratorArgFactory { + return () => ({ + alias: ["named"], + description: "Generate a named export, instead of a default export", + name: "namedExport", + type: "boolean", + }); +} + export function nested({ description, }: { diff --git a/src/generators/generators.ts b/src/generators/generators.ts index ba608a6..bcfafa8 100644 --- a/src/generators/generators.ts +++ b/src/generators/generators.ts @@ -3,6 +3,7 @@ import { classBased, defineGenerator, defineTestGenerator, + namedExport, nested, test, testGeneratorName, @@ -21,6 +22,7 @@ export const generators: Generator[] = [ defineGenerator({ args: [ classBased({ functionBasedName: "template-only" }), + namedExport(), nested({ description: "Generate a nested colocated component, e.g. `foo/bar/index.gts`", @@ -49,7 +51,7 @@ export const generators: Generator[] = [ }), defineGenerator({ - args: [classBased(), test(), typescript()], + args: [classBased(), namedExport(), test(), typescript()], name: "helper", }), @@ -60,7 +62,7 @@ export const generators: Generator[] = [ }), defineGenerator({ - args: [classBased(), test(), typescript()], + args: [classBased(), namedExport(), test(), typescript()], name: "modifier", }), @@ -93,7 +95,7 @@ export const generators: Generator[] = [ }), defineGenerator({ - args: [test(), typescript()], + args: [namedExport(), test(), typescript()], name: "util", }), diff --git a/templates/component/component.class-based.gjs b/templates/component/component.class-based.gjs index efbc0ff..7855815 100644 --- a/templates/component/component.class-based.gjs +++ b/templates/component/component.class-based.gjs @@ -1,5 +1,5 @@ import Component from '@glimmer/component'; -export default class {{name.pascal}} extends Component { +{{#if args.namedExport}}export{{else}}export default{{/if}} class {{name.pascal}} extends Component { } diff --git a/templates/component/component.class-based.gts b/templates/component/component.class-based.gts index 1bc552d..15e8082 100644 --- a/templates/component/component.class-based.gts +++ b/templates/component/component.class-based.gts @@ -8,7 +8,7 @@ export interface {{name.signature}} { Element: null; } -export default class {{name.pascal}} extends Component<{{name.signature}}> { +{{#if args.namedExport}}export{{else}}export default{{/if}} class {{name.pascal}} extends Component<{{name.signature}}> { diff --git a/templates/component/component.template-only.gts b/templates/component/component.template-only.gts index d93ff6c..98f8838 100644 --- a/templates/component/component.template-only.gts +++ b/templates/component/component.template-only.gts @@ -8,6 +8,8 @@ export interface {{name.signature}} { Element: null; } -const {{name.pascal}}: TOC<{{name.signature}}> = ; +{{#if args.namedExport}}export {{/if}}const {{name.pascal}}: TOC<{{name.signature}}> = ; +{{#unless args.namedExport}} export default {{name.pascal}}; +{{/unless}} diff --git a/templates/helper/helper.class-based.js b/templates/helper/helper.class-based.js index d3f7fc2..5963c93 100644 --- a/templates/helper/helper.class-based.js +++ b/templates/helper/helper.class-based.js @@ -1,6 +1,6 @@ import Helper from '@ember/component/helper'; -export default class {{name.camel}} extends Helper { +{{#if args.namedExport}}export{{else}}export default{{/if}} class {{name.camel}} extends Helper { compute(positional, named) { return positional; } diff --git a/templates/helper/helper.class-based.ts b/templates/helper/helper.class-based.ts index 38153d8..a6ad8c9 100644 --- a/templates/helper/helper.class-based.ts +++ b/templates/helper/helper.class-based.ts @@ -12,7 +12,7 @@ export interface {{name.signature}} { Return: Return; } -export default class {{name.camel}} extends Helper<{{name.signature}}> { +{{#if args.namedExport}}export{{else}}export default{{/if}} class {{name.camel}} extends Helper<{{name.signature}}> { compute(positional: Positional, named: Named): Return { return positional; } diff --git a/templates/helper/helper.function-based.js b/templates/helper/helper.function-based.js index 477d464..12b9fd8 100644 --- a/templates/helper/helper.function-based.js +++ b/templates/helper/helper.function-based.js @@ -1,3 +1,3 @@ -export default function {{name.camel}}(positional, named) { +{{#if args.namedExport}}export{{else}}export default{{/if}} function {{name.camel}}(positional, named) { return positional; } diff --git a/templates/modifier/modifier.class-based.js b/templates/modifier/modifier.class-based.js index 4ab3bd6..78647f7 100644 --- a/templates/modifier/modifier.class-based.js +++ b/templates/modifier/modifier.class-based.js @@ -1,5 +1,5 @@ import Modifier from 'ember-modifier'; -export default class {{name.camel}} extends Modifier { +{{#if args.namedExport}}export{{else}}export default{{/if}} class {{name.camel}} extends Modifier { modify(element, positional, named) {} } diff --git a/templates/modifier/modifier.class-based.ts b/templates/modifier/modifier.class-based.ts index 5251dec..f88ef0a 100644 --- a/templates/modifier/modifier.class-based.ts +++ b/templates/modifier/modifier.class-based.ts @@ -12,6 +12,6 @@ export interface {{name.signature}} { Element: Element; } -export default class {{name.camel}} extends Modifier<{{name.signature}}> { +{{#if args.namedExport}}export{{else}}export default{{/if}} class {{name.camel}} extends Modifier<{{name.signature}}> { modify(element: Element, positional: Positional, named: Named) {} } diff --git a/templates/modifier/modifier.function-based.js b/templates/modifier/modifier.function-based.js index e12995c..9a494eb 100644 --- a/templates/modifier/modifier.function-based.js +++ b/templates/modifier/modifier.function-based.js @@ -1,3 +1,3 @@ import { modifier } from 'ember-modifier'; -export default modifier(function {{name.camel}}(element, positional, named) {}); +{{#if args.namedExport}}export const {{name.camel}} ={{else}}export default{{/if}} modifier(function {{name.camel}}(element, positional, named) {}); diff --git a/templates/modifier/modifier.function-based.ts b/templates/modifier/modifier.function-based.ts index d4c58eb..875208d 100644 --- a/templates/modifier/modifier.function-based.ts +++ b/templates/modifier/modifier.function-based.ts @@ -8,4 +8,4 @@ export interface {{name.signature}} { Element: null; } -export default modifier<{{name.signature}}>(function {{name.camel}}(element, positional, named) {}); +{{#if args.namedExport}}export const {{name.camel}} ={{else}}export default{{/if}} modifier<{{name.signature}}>(function {{name.camel}}(element, positional, named) {}); diff --git a/templates/util/util.js b/templates/util/util.js index 29ed98b..7126931 100644 --- a/templates/util/util.js +++ b/templates/util/util.js @@ -1,3 +1,3 @@ -export default function {{name.camel}}() { +{{#if args.namedExport}}export{{else}}export default{{/if}} function {{name.camel}}() { return true; } diff --git a/test/generators/__snapshots__/component.test.ts.snap b/test/generators/__snapshots__/component.test.ts.snap index 0b7b54a..a53ddef 100644 --- a/test/generators/__snapshots__/component.test.ts.snap +++ b/test/generators/__snapshots__/component.test.ts.snap @@ -28,6 +28,49 @@ export default class Foo extends Component { " `; +exports[`generates a named export > --classBased --typescript 1`] = ` +"import Component from '@glimmer/component'; + +export interface FooSignature { + Args: {}; + Blocks: { + default: []; + }; + Element: null; +} + +export class Foo extends Component { + +} +" +`; + +exports[`generates a named export > --classBased 1`] = ` +"import Component from '@glimmer/component'; + +export class Foo extends Component { + +} +" +`; + +exports[`generates a named export > --typescript 1`] = ` +"import type { TOC } from '@ember/component/template-only'; + +export interface FooSignature { + Args: {}; + Blocks: { + default: []; + }; + Element: null; +} + +export const Foo: TOC = ; +" +`; + exports[`generates a nested colocated template-only \`.gjs\` component 1`] = ` " " diff --git a/test/generators/__snapshots__/helper.test.ts.snap b/test/generators/__snapshots__/helper.test.ts.snap index 5e4ad60..c73f963 100644 --- a/test/generators/__snapshots__/helper.test.ts.snap +++ b/test/generators/__snapshots__/helper.test.ts.snap @@ -62,6 +62,54 @@ exports[`generates a function-based \`.ts\` helper at a custom path 1`] = ` " `; +exports[`generates a named export > --classBased --typescript 1`] = ` +"import Helper from '@ember/component/helper'; + +type Named = {}; +type Positional = []; +type Return = Positional; + +export interface FooSignature { + Args: { + Named: Named; + Positional: Positional; + }; + Return: Return; +} + +export class foo extends Helper { + compute(positional: Positional, named: Named): Return { + return positional; + } +} +" +`; + +exports[`generates a named export > --classBased 1`] = ` +"import Helper from '@ember/component/helper'; + +export class foo extends Helper { + compute(positional, named) { + return positional; + } +} +" +`; + +exports[`generates a named export > --typescript 1`] = ` +"export function foo(positional, named) { + return positional; +} +" +`; + +exports[`generates a named export > no extra args 1`] = ` +"export function foo(positional, named) { + return positional; +} +" +`; + exports[`generates a nested function-based \`.js\` helper 1`] = ` "export default function fooBar(positional, named) { return positional; diff --git a/test/generators/__snapshots__/modifier.test.ts.snap b/test/generators/__snapshots__/modifier.test.ts.snap index 0cb409a..ce0028d 100644 --- a/test/generators/__snapshots__/modifier.test.ts.snap +++ b/test/generators/__snapshots__/modifier.test.ts.snap @@ -74,6 +74,58 @@ export default modifier(function foo(element, positional, named) { " `; +exports[`generates a named export > --classBased --typescript 1`] = ` +"import Modifier from 'ember-modifier'; + +type Named = {}; +type Positional = []; +type Element = null; + +export interface FooSignature { + Args: { + Named: Named; + Positional: Positional; + }; + Element: Element; +} + +export class foo extends Modifier { + modify(element: Element, positional: Positional, named: Named) {} +} +" +`; + +exports[`generates a named export > --classBased 1`] = ` +"import Modifier from 'ember-modifier'; + +export class foo extends Modifier { + modify(element, positional, named) {} +} +" +`; + +exports[`generates a named export > --typescript 1`] = ` +"import { modifier } from 'ember-modifier'; + +export interface FooSignature { + Args: { + Named: {}; + Positional: []; + }; + Element: null; +} + +export const foo = modifier(function foo(element, positional, named) {}); +" +`; + +exports[`generates a named export > no extra args 1`] = ` +"import { modifier } from 'ember-modifier'; + +export const foo = modifier(function foo(element, positional, named) {}); +" +`; + exports[`generates a nested function-based \`.js\` modifier 1`] = ` "import { modifier } from 'ember-modifier'; diff --git a/test/generators/__snapshots__/util.test.ts.snap b/test/generators/__snapshots__/util.test.ts.snap index 43c14e4..e9abb7e 100644 --- a/test/generators/__snapshots__/util.test.ts.snap +++ b/test/generators/__snapshots__/util.test.ts.snap @@ -28,6 +28,20 @@ exports[`generates a \`.ts\` util at a custom path 1`] = ` " `; +exports[`generates a named export > --typescript 1`] = ` +"export function foo() { + return true; +} +" +`; + +exports[`generates a named export > no extra args 1`] = ` +"export function foo() { + return true; +} +" +`; + exports[`generates a nested \`.js\` util 1`] = ` "export default function fooBar() { return true; diff --git a/test/generators/component.test.ts b/test/generators/component.test.ts index 0032e85..da58f70 100644 --- a/test/generators/component.test.ts +++ b/test/generators/component.test.ts @@ -1,4 +1,4 @@ -import { afterEach, it } from "vitest"; +import { afterEach, describe, it } from "vitest"; import { Package } from "../helpers.ts"; let pkg: Package; @@ -107,3 +107,27 @@ it("destroys a component", async (ctx) => { ctx.expect(await pkg.pathExists("src/components/foo.gjs")).to.equal(false); }); + +describe("generates a named export", () => { + for (const args of [ + ["--classBased"], + ["--classBased", "--typescript"], + ["--typescript"], + ]) { + it(args.length ? args.join(" ") : "no extra args", async (ctx) => { + pkg = await Package.create("v2-addon"); + + await pkg.gember("component", "foo", "--namedExport", ...args); + + const content = await pkg.readFile( + addExtension("src/components/foo", args), + ); + + ctx.expect(content).toMatchSnapshot(); + }); + } +}); + +function addExtension(path: string, args: string[]): string { + return path + (args.includes("--typescript") ? ".gts" : ".gjs"); +} diff --git a/test/generators/helper.test.ts b/test/generators/helper.test.ts index 9ba6894..f1d08c5 100644 --- a/test/generators/helper.test.ts +++ b/test/generators/helper.test.ts @@ -1,4 +1,4 @@ -import { afterEach, it } from "vitest"; +import { afterEach, describe, it } from "vitest"; import { Package } from "../helpers.ts"; let pkg: Package; @@ -97,3 +97,26 @@ it("destroys a helper", async (ctx) => { ctx.expect(await pkg.pathExists("src/helpers/foo.js")).to.equal(false); }); + +describe("generates a named export", () => { + for (const args of [ + [], + ["--classBased"], + ["--classBased", "--typescript"], + ["--typescript"], + ]) { + it(args.length ? args.join(" ") : "no extra args", async (ctx) => { + pkg = await Package.create("v2-addon"); + + await pkg.gember("helper", "foo", "--namedExport", ...args); + + const content = await pkg.readFile(addExtension("src/helpers/foo", args)); + + ctx.expect(content).toMatchSnapshot(); + }); + } +}); + +function addExtension(path: string, args: string[]): string { + return path + (args.includes("--typescript") ? ".ts" : ".js"); +} diff --git a/test/generators/modifier.test.ts b/test/generators/modifier.test.ts index 8a3964f..2c4b6d3 100644 --- a/test/generators/modifier.test.ts +++ b/test/generators/modifier.test.ts @@ -1,4 +1,4 @@ -import { afterEach, it } from "vitest"; +import { afterEach, describe, it } from "vitest"; import { Package } from "../helpers.ts"; let pkg: Package; @@ -97,3 +97,28 @@ it("destroys a modifier", async (ctx) => { ctx.expect(await pkg.pathExists("src/modifiers/foo.js")).to.equal(false); }); + +describe("generates a named export", () => { + for (const args of [ + [], + ["--classBased"], + ["--classBased", "--typescript"], + ["--typescript"], + ]) { + it(args.length ? args.join(" ") : "no extra args", async (ctx) => { + pkg = await Package.create("v2-addon"); + + await pkg.gember("modifier", "foo", "--namedExport", ...args); + + const content = await pkg.readFile( + addExtension("src/modifiers/foo", args), + ); + + ctx.expect(content).toMatchSnapshot(); + }); + } +}); + +function addExtension(path: string, args: string[]): string { + return path + (args.includes("--typescript") ? ".ts" : ".js"); +} diff --git a/test/generators/util.test.ts b/test/generators/util.test.ts index 14783ce..64f112c 100644 --- a/test/generators/util.test.ts +++ b/test/generators/util.test.ts @@ -1,4 +1,4 @@ -import { afterEach, it } from "vitest"; +import { afterEach, describe, it } from "vitest"; import { Package } from "../helpers.ts"; let pkg: Package; @@ -87,3 +87,21 @@ it("destroys a util", async (ctx) => { ctx.expect(await pkg.pathExists("src/utils/foo.js")).to.equal(false); }); + +describe("generates a named export", () => { + for (const args of [[], ["--typescript"]]) { + it(args.length ? args.join(" ") : "no extra args", async (ctx) => { + pkg = await Package.create("v2-addon"); + + await pkg.gember("util", "foo", "--namedExport", ...args); + + const content = await pkg.readFile(addExtension("src/utils/foo", args)); + + ctx.expect(content).toMatchSnapshot(); + }); + } +}); + +function addExtension(path: string, args: string[]): string { + return path + (args.includes("--typescript") ? ".ts" : ".js"); +}