Skip to content

Replace per-casing EJS variables with a generic template casing helper #81

@nick-pape

Description

@nick-pape

Background

Currently, the CLI pre-computes a fixed set of casing variants from the component name and passes each as a separate EJS variable:

// apps/spfx-cli/src/cli/CreateAction.ts
const componentNameCamelCase    = camelCase(componentName);
const componentNameHyphenCase   = kebabCase(componentName);
const componentNameCapitalCase  = upperFirst(camelCase(componentName));
const componentNameAllCaps      = snakeCase(componentName).toUpperCase();

Each variant is then:

  1. Passed individually into the EJS render context
  2. Declared individually in every template's contextSchema in template.json

This means adding any new casing requires touching CreateAction.ts, the render context, and every template.json.

Problem

  • Fragile: templates that forget to declare a variable in contextSchema silently receive undefined
  • Verbose: contextSchema in every template.json lists the same ~8 variables
  • Not extensible: a one-off need (e.g., UPPER_SNAKE_CASE for registry IDs in ACE templates) required a new top-level variable (componentNameAllCaps) across all templates
  • Naming ambiguity: it's unclear which variable to use where — e.g., componentNameAllCaps vs componentNameCapitalCase vs componentNameUnescaped

Proposed Solution

Pass a single casing utility object (or helper functions) into the EJS render context, so templates can derive any casing on the fly without pre-computation:

Option A — Helper object with named transforms

<%= casing.allCaps(componentName) %>       → DATA_VISUALIZATION
<%= casing.camel(componentName) %>         → dataVisualization
<%= casing.pascal(componentName) %>        → DataVisualization
<%= casing.kebab(componentName) %>         → data-visualization
<%= casing.snake(componentName) %>         → data_visualization

The casing object is injected once into the render context:

import { camelCase, kebabCase, snakeCase, upperFirst } from 'lodash';

const casing = {
  camel:   (s: string) => camelCase(s),
  pascal:  (s: string) => upperFirst(camelCase(s)),
  kebab:   (s: string) => kebabCase(s),
  snake:   (s: string) => snakeCase(s),
  allCaps: (s: string) => snakeCase(s).toUpperCase(),
};

Option B — Smart string object

Pass a single name object that exposes all casings as properties:

<%= name.allCaps %>    → DATA_VISUALIZATION
<%= name.camel %>      → dataVisualization
<%= name.pascal %>     → DataVisualization

Option C — Keep pre-computing but simplify contextSchema

Instead of per-template contextSchema declarations, define a single shared schema in the template engine that all templates inherit. Templates only need to override if they use a non-standard subset.

Benefits

  • Templates are self-documenting about the exact transform being applied
  • Adding new casing variants requires changing one place only
  • contextSchema in template.json can be simplified or removed entirely
  • Eliminates naming confusion between variants

Migration

The existing componentNameCamelCase, componentNameCapitalCase, etc. variables can be kept as aliases during a transition period, then deprecated.

Related

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions