Repository: teleporthq/teleport-code-generators
Pull request #373 (2019-09-27 15:28:35)

WIP: Refactor/page and component output options

codecov[bot]: # [Codecov](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373?src=pr&el=h1) Report
> Merging [#373](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373?src=pr&el=desc) into [master](https://codecov.io/gh/teleporthq/teleport-code-generators/commit/8055fbaa2e4e8837fec2a19b1d9eeb49c4221a00?src=pr&el=desc) will **decrease** coverage by `0.48%`.
> The diff coverage is `79.31%`.

[![Impacted file tree graph](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/graphs/tree.svg?width=650&token=LI2WPEFm1R&height=150&src=pr)](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #373      +/-   ##
==========================================
- Coverage   81.55%   81.07%   -0.49%     
==========================================
  Files         131      131              
  Lines        4056     4148      +92     
  Branches      852      883      +31     
==========================================
+ Hits         3308     3363      +55     
- Misses        706      738      +32     
- Partials       42       47       +5
```


| [Impacted Files](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...es/teleport-project-generator/src/file-handlers.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtcHJvamVjdC1nZW5lcmF0b3Ivc3JjL2ZpbGUtaGFuZGxlcnMudHM=) | `75.65% <ø> (+0.98%)` | :arrow_up: |
| [...ort-shared/src/node-handlers/node-to-html/index.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtc2hhcmVkL3NyYy9ub2RlLWhhbmRsZXJzL25vZGUtdG8taHRtbC9pbmRleC50cw==) | `73.23% <0%> (-0.38%)` | :arrow_down: |
| [...es/teleport-plugin-vue-base-component/src/utils.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtcGx1Z2luLXZ1ZS1iYXNlLWNvbXBvbmVudC9zcmMvdXRpbHMudHM=) | `52.7% <100%> (+0.64%)` | :arrow_up: |
| [packages/teleport-uidl-resolver/src/resolver.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtdWlkbC1yZXNvbHZlci9zcmMvcmVzb2x2ZXIudHM=) | `94.59% <100%> (+0.84%)` | :arrow_up: |
| [packages/teleport-project-generator/src/index.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtcHJvamVjdC1nZW5lcmF0b3Ivc3JjL2luZGV4LnRz) | `83% <100%> (-0.17%)` | :arrow_down: |
| [...teleport-plugin-preact-base-component/src/index.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtcGx1Z2luLXByZWFjdC1iYXNlLWNvbXBvbmVudC9zcmMvaW5kZXgudHM=) | `100% <100%> (ø)` | :arrow_up: |
| [...kages/teleport-plugin-vue-app-routing/src/index.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtcGx1Z2luLXZ1ZS1hcHAtcm91dGluZy9zcmMvaW5kZXgudHM=) | `100% <100%> (+3.57%)` | :arrow_up: |
| [...ges/teleport-plugin-react-app-routing/src/index.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtcGx1Z2luLXJlYWN0LWFwcC1yb3V0aW5nL3NyYy9pbmRleC50cw==) | `92.3% <100%> (+3.11%)` | :arrow_up: |
| [packages/teleport-uidl-resolver/src/utils.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtdWlkbC1yZXNvbHZlci9zcmMvdXRpbHMudHM=) | `59.69% <100%> (-0.62%)` | :arrow_down: |
| [...eleport-plugin-stencil-base-component/src/index.ts](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree#diff-cGFja2FnZXMvdGVsZXBvcnQtcGx1Z2luLXN0ZW5jaWwtYmFzZS1jb21wb25lbnQvc3JjL2luZGV4LnRz) | `86.11% <100%> (+0.39%)` | :arrow_up: |
| ... and [14 more](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373?src=pr&el=footer). Last update [8055fba...52971fe](https://codecov.io/gh/teleporthq/teleport-code-generators/pull/373?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/packages/teleport-plugin-angular-base-component/src/index.ts b/packages/teleport-plugin-angular-base-component/src/index.ts
index b2e96fff..238b96d8 100644
--- a/packages/teleport-plugin-angular-base-component/src/index.ts
+++ b/packages/teleport-plugin-angular-base-component/src/index.ts
@@ -84,7 +84,7 @@ export const createAngularComponentPlugin: ComponentPluginFactory<AngularPluginC
           const index = useIndex ? `; index as index` : ''
           return `let ${iteratorName} of ${iteratedCollection}${index}`
         },
-        customElementTagName: (value) => `app-${value}`,
+        customElementTagName: (value) => `app-${StringUtils.camelCaseToDashCase(value)}`,
         dependencyHandling: 'ignore',
       }
     )
@@ -100,8 +100,9 @@ export const createAngularComponentPlugin: ComponentPluginFactory<AngularPluginC
       linkAfter: [],
     })
 
+    const componentName = UIDLUtils.getComponentClassName(uidl)
     const params = {
-      selector: `app-${StringUtils.camelCaseToDashCase(uidl.name)}`,
+      selector: `app-${StringUtils.camelCaseToDashCase(componentName)}`,
       templateUrl: `${UIDLUtils.getComponentFileName(uidl)}.${FileType.HTML}`,
     }
     const componentDecoratorAST = ASTBuilders.createComponentDecorator(params)
@@ -133,7 +134,6 @@ export const createAngularComponentPlugin: ComponentPluginFactory<AngularPluginC
 
     const exportAST = generateExportAST(
       uidl,
-      uidl.name,
       propDefinitions,
       stateDefinitions,
       dataObject,
diff --git a/packages/teleport-plugin-angular-base-component/src/utils.ts b/packages/teleport-plugin-angular-base-component/src/utils.ts
index 71c04010..4bf5fec0 100644
--- a/packages/teleport-plugin-angular-base-component/src/utils.ts
+++ b/packages/teleport-plugin-angular-base-component/src/utils.ts
@@ -1,5 +1,5 @@
 import * as types from '@babel/types'
-import { ASTUtils, ASTBuilders } from '@teleporthq/teleport-shared'
+import { ASTUtils, ASTBuilders, UIDLUtils } from '@teleporthq/teleport-shared'
 import {
   UIDLMetaTag,
   ComponentUIDL,
@@ -11,13 +11,13 @@ import {
 
 export const generateExportAST = (
   uidl: ComponentUIDL,
-  componentName: string,
   propDefinitions: Record<string, UIDLPropDefinition>,
   stateDefinitions: Record<string, UIDLStateDefinition>,
   dataObject: Record<string, any>,
   methodsObject: Record<string, UIDLEventHandlerStatement[]>,
   t = types
 ) => {
+  const componentName = UIDLUtils.getComponentClassName(uidl)
   let angularMethodsAST: types.ClassMethod[] = []
   if (Object.keys(methodsObject).length > 0) {
     angularMethodsAST = createMethodsObject(methodsObject, propDefinitions)
diff --git a/packages/teleport-plugin-angular-module/src/index.ts b/packages/teleport-plugin-angular-module/src/index.ts
index fa722d4e..b667a327 100644
--- a/packages/teleport-plugin-angular-module/src/index.ts
+++ b/packages/teleport-plugin-angular-module/src/index.ts
@@ -1,5 +1,5 @@
 import * as types from '@babel/types'
-import { UIDLUtils, StringUtils } from '@teleporthq/teleport-shared'
+import { UIDLUtils } from '@teleporthq/teleport-shared'
 import {
   ComponentPluginFactory,
   ComponentPlugin,
@@ -74,29 +74,39 @@ export const createAngularModulePlugin: ComponentPluginFactory<AngularRoutingCon
         {
           dependencies.ComponentsModule = constructRouteForComponentsModule('../..')
           dependencies.CommonModule = ANGULAR_COMMON_MODULE
-          const componentName = `${uidl.name}Component`
-          dependencies[componentName] = constructLocalDependency(uidl.outputOptions.fileName)
+          const componentName = UIDLUtils.getComponentClassName(uidl)
+          const componentClassName = `${componentName}Component`
+          const fileName = UIDLUtils.getComponentFileName(uidl)
+          dependencies[componentClassName] = constructLocalDependency(fileName)
 
-          routesAST = createPageRouteAST(componentName)
-          ngModuleAST = createPageModuleModuleDecorator(componentName)
+          routesAST = createPageRouteAST(componentClassName)
+          ngModuleAST = createPageModuleModuleDecorator(componentClassName)
           moduleDecoratorAST = createExportModuleAST(uidl.outputOptions.moduleName)
 
           // Acording to widely followed convention module should have .module in its name
-          uidl.outputOptions.fileName = `${uidl.outputOptions.fileName}.module`
+          uidl.outputOptions.fileName = fileName.replace('.component', '.module')
         }
         break
       case 'component':
         {
           dependencies.CommonModule = ANGULAR_COMMON_MODULE
+
           // Looping through all components and importing them into component module
-          moduleComponents.forEach(
-            (component) =>
-              (dependencies[`${component}Component`] = constructComponentDependency(
-                StringUtils.camelCaseToDashCase(component)
-              ))
+          Object.keys(moduleComponents).forEach((componentKey) => {
+            const component = moduleComponents[componentKey]
+            const componentClassName = UIDLUtils.getComponentClassName(component)
+            const componentFileName = UIDLUtils.getComponentFileName(component)
+            const componentFolderPath = UIDLUtils.getComponentFolderPath(component)
+            dependencies[`${componentClassName}Component`] = constructComponentDependency(
+              componentFolderPath,
+              componentFileName
+            )
+          })
+
+          const componentClassNames = Object.keys(moduleComponents).map((componentKey) =>
+            UIDLUtils.getComponentClassName(moduleComponents[componentKey])
           )
-
-          ngModuleAST = createComponentModuleDecorator(moduleComponents)
+          ngModuleAST = createComponentModuleDecorator(componentClassNames)
           moduleDecoratorAST = createExportModuleAST('ComponentsModule')
         }
         break
diff --git a/packages/teleport-plugin-angular-module/src/utils.ts b/packages/teleport-plugin-angular-module/src/utils.ts
index 160e6e43..b3a09f7b 100644
--- a/packages/teleport-plugin-angular-module/src/utils.ts
+++ b/packages/teleport-plugin-angular-module/src/utils.ts
@@ -4,7 +4,7 @@ import {
   UIDLConditionalNode,
   UIDLStateDefinition,
 } from '@teleporthq/teleport-types'
-import { UIDLUtils, StringUtils } from '@teleporthq/teleport-shared'
+import { StringUtils } from '@teleporthq/teleport-shared'
 
 export const createPageModuleModuleDecorator = (componentName: string, t = types) => {
   const imports: types.ObjectProperty = t.objectProperty(
@@ -35,10 +35,10 @@ export const createPageModuleModuleDecorator = (componentName: string, t = types
   )
 }
 
-export const createComponentModuleDecorator = (components?: string[], t = types) => {
+export const createComponentModuleDecorator = (componentNames?: string[], t = types) => {
   const componentsList =
-    components && components.length > 0
-      ? components.map((component) => t.identifier(`${component}Component`))
+    componentNames && componentNames.length > 0
+      ? componentNames.map((componentName) => t.identifier(`${componentName}Component`))
       : []
 
   const declerations: types.ObjectProperty = t.objectProperty(
@@ -110,18 +110,16 @@ export const createRoutesAST = (
   // TODO: Need to generate type annotation for routes variable, currently babel throwing erro
   const routesObject = routes.map((conditionalNode) => {
     const { value: routeKey } = conditionalNode.content
-    const { fileName: pageName, navLink } = UIDLUtils.extractPageOptions(
-      stateDefinitions.route,
-      routeKey.toString()
-    )
+    const pageDefinition = stateDefinitions.route.values.find((route) => route.value === routeKey)
+    const { navLink, fileName } = pageDefinition.pageOptions
 
     return t.objectExpression([
       t.objectProperty(t.identifier('path'), t.stringLiteral(navLink.replace('/', ''))),
       t.objectProperty(
         t.identifier('loadChildren'),
         t.stringLiteral(
-          `./pages/${pageName}/${pageName}.module#${StringUtils.dashCaseToUpperCamelCase(
-            `${pageName}-module`
+          `./pages/${fileName}/${fileName}.module#${StringUtils.dashCaseToUpperCamelCase(
+            `${fileName}-module`
           )}`
         )
       ),
@@ -147,10 +145,10 @@ export const createPageRouteAST = (componentName: string, t = types) => {
   ])
 }
 
-export const constructLocalDependency = (componentName: string) => {
+export const constructLocalDependency = (fileName: string) => {
   const dependency: UIDLDependency = {
     type: 'local',
-    path: `./${componentName}.component`,
+    path: `./${fileName}`,
     meta: {
       namedImport: true,
     },
@@ -158,10 +156,10 @@ export const constructLocalDependency = (componentName: string) => {
   return dependency
 }
 
-export const constructComponentDependency = (componentName: string) => {
+export const constructComponentDependency = (folderPath: string[], fileName: string) => {
   const dependency: UIDLDependency = {
     type: 'local',
-    path: `./${componentName}/${componentName}.component`,
+    path: `./${folderPath.join('/')}/${fileName}`,
     meta: {
       namedImport: true,
     },
diff --git a/packages/teleport-plugin-jsx-proptypes/src/index.ts b/packages/teleport-plugin-jsx-proptypes/src/index.ts
index f1e3537a..2f0eee00 100644
--- a/packages/teleport-plugin-jsx-proptypes/src/index.ts
+++ b/packages/teleport-plugin-jsx-proptypes/src/index.ts
@@ -5,6 +5,7 @@ import {
   ChunkType,
   FileType,
 } from '@teleporthq/teleport-types'
+import { UIDLUtils } from '@teleporthq/teleport-shared'
 
 interface PropTypesConfig {
   componentChunkName?: string
@@ -23,8 +24,8 @@ export const createPropTypesPlugin: ComponentPluginFactory<PropTypesConfig> = (c
 
   const propTypesPlugin: ComponentPlugin = async (structure) => {
     const { uidl, chunks, dependencies } = structure
-    const { name } = uidl
 
+    const componentClassName = UIDLUtils.getComponentClassName(uidl)
     const componentChunk = chunks.find((chunk) => chunk.name === componentChunkName)
     const exportChunk = chunks.find((chunk) => chunk.name === exportComponentName)
 
@@ -43,7 +44,11 @@ export const createPropTypesPlugin: ComponentPluginFactory<PropTypesConfig> = (c
       (prop) => typeof uidl.propDefinitions[prop].defaultValue !== 'undefined'
     )
 
-    const typesOfPropsAst = buildTypesOfPropsAst(name, 'PropTypes', uidl.propDefinitions)
+    const typesOfPropsAst = buildTypesOfPropsAst(
+      componentClassName,
+      'PropTypes',
+      uidl.propDefinitions
+    )
 
     if (!hasDefaultProps && !typesOfPropsAst) {
       return structure
@@ -56,7 +61,7 @@ export const createPropTypesPlugin: ComponentPluginFactory<PropTypesConfig> = (c
     }
 
     if (hasDefaultProps) {
-      const defaultPropsAst = buildDefaultPropsAst(name, uidl.propDefinitions)
+      const defaultPropsAst = buildDefaultPropsAst(componentClassName, uidl.propDefinitions)
       chunks.push({
         type: ChunkType.AST,
         fileType: FileType.JS,
diff --git a/packages/teleport-plugin-preact-base-component/src/index.ts b/packages/teleport-plugin-preact-base-component/src/index.ts
index 6b84905b..8ae028d7 100644
--- a/packages/teleport-plugin-preact-base-component/src/index.ts
+++ b/packages/teleport-plugin-preact-base-component/src/index.ts
@@ -1,5 +1,10 @@
 import { createClassComponent, createPureComponent } from './utils'
-import { createJSXSyntax, JSXGenerationOptions, ASTBuilders } from '@teleporthq/teleport-shared'
+import {
+  createJSXSyntax,
+  JSXGenerationOptions,
+  ASTBuilders,
+  UIDLUtils,
+} from '@teleporthq/teleport-shared'
 
 import {
   ComponentPluginFactory,
@@ -62,9 +67,10 @@ export const createPreactComponentPlugin: ComponentPluginFactory<PreactPluginCon
       dependencies.Component = PREACT_COMPONENT_DEPENDENCY
     }
 
+    const componentName = UIDLUtils.getComponentClassName(uidl)
     const preactComponent = usePureComponent
-      ? createPureComponent(uidl.name, propDefinitions, jsxTagStructure)
-      : createClassComponent(uidl.name, propDefinitions, stateDefinitions, jsxTagStructure)
+      ? createPureComponent(componentName, propDefinitions, jsxTagStructure)
+      : createClassComponent(componentName, propDefinitions, stateDefinitions, jsxTagStructure)
 
     structure.chunks.push({
       type: ChunkType.AST,
@@ -82,7 +88,7 @@ export const createPreactComponentPlugin: ComponentPluginFactory<PreactPluginCon
       type: ChunkType.AST,
       fileType: FileType.JS,
       name: exportChunkName,
-      content: ASTBuilders.createDefaultExport(uidl.name),
+      content: ASTBuilders.createDefaultExport(componentName),
       linkAfter: [componentChunkName],
     })
 
diff --git a/packages/teleport-plugin-react-app-routing/src/index.ts b/packages/teleport-plugin-react-app-routing/src/index.ts
index 783d4456..944899ed 100644
--- a/packages/teleport-plugin-react-app-routing/src/index.ts
+++ b/packages/teleport-plugin-react-app-routing/src/index.ts
@@ -10,6 +10,7 @@ import {
   ComponentPlugin,
   ChunkType,
   FileType,
+  UIDLPageOptions,
 } from '@teleporthq/teleport-types'
 
 interface AppRoutingComponentConfig {
@@ -46,11 +47,11 @@ export const createReactAppRoutingPlugin: ComponentPluginFactory<AppRoutingCompo
 
     const routeJSXDefinitions = routes.map((conditionalNode) => {
       const { value: routeKey } = conditionalNode.content
+      const routeValues = stateDefinitions.route.values || []
 
-      const { fileName: pageName, componentName, navLink } = UIDLUtils.extractPageOptions(
-        stateDefinitions.route,
-        routeKey.toString()
-      )
+      const pageDefinition = routeValues.find((route) => route.value === routeKey)
+      const defaultOptions: UIDLPageOptions = {}
+      const { fileName, componentName, navLink } = pageDefinition.pageOptions || defaultOptions
 
       /* If pages are exported in their own folder and in custom file names.
          Import statements must then be:
@@ -60,13 +61,11 @@ export const createReactAppRoutingPlugin: ComponentPluginFactory<AppRoutingCompo
          so the `/component` suffix is computed below.
       */
       const pageStrategyOptions = (strategy && strategy.pages.options) || {}
-      const pageComponentSuffix = pageStrategyOptions.createFolderForEachComponent
-        ? '/' + (pageStrategyOptions.customComponentFileName || 'index')
-        : ''
+      const pageComponentSuffix = pageStrategyOptions.createFolderForEachComponent ? '/index' : ''
 
       dependencies[componentName] = {
         type: 'local',
-        path: `${pageDependencyPrefix}${pageName}${pageComponentSuffix}`,
+        path: `${pageDependencyPrefix}${fileName}${pageComponentSuffix}`,
       }
 
       return constructRouteJSX(flavor, componentName, navLink)
diff --git a/packages/teleport-plugin-react-base-component/src/index.ts b/packages/teleport-plugin-react-base-component/src/index.ts
index 7d4b1ad4..6c267b84 100644
--- a/packages/teleport-plugin-react-base-component/src/index.ts
+++ b/packages/teleport-plugin-react-base-component/src/index.ts
@@ -1,5 +1,10 @@
 import { createPureComponent } from './utils'
-import { createJSXSyntax, JSXGenerationOptions, ASTBuilders } from '@teleporthq/teleport-shared'
+import {
+  createJSXSyntax,
+  JSXGenerationOptions,
+  ASTBuilders,
+  UIDLUtils,
+} from '@teleporthq/teleport-shared'
 
 import {
   ComponentPluginFactory,
@@ -63,7 +68,8 @@ export const createReactComponentPlugin: ComponentPluginFactory<ReactPluginConfi
 
     const jsxTagStructure = createJSXSyntax(uidl.node, jsxParams, jsxOptions)
 
-    const pureComponent = createPureComponent(uidl.name, stateDefinitions, jsxTagStructure)
+    const componentName = UIDLUtils.getComponentClassName(uidl)
+    const pureComponent = createPureComponent(componentName, stateDefinitions, jsxTagStructure)
 
     structure.chunks.push({
       type: ChunkType.AST,
@@ -81,7 +87,7 @@ export const createReactComponentPlugin: ComponentPluginFactory<ReactPluginConfi
       type: ChunkType.AST,
       fileType: FileType.JS,
       name: exportChunkName,
-      content: ASTBuilders.createDefaultExport(uidl.name),
+      content: ASTBuilders.createDefaultExport(componentName),
       linkAfter: [componentChunkName],
     })
 
diff --git a/packages/teleport-plugin-react-jss/src/index.ts b/packages/teleport-plugin-react-jss/src/index.ts
index 98c141a1..9d7a7a80 100644
--- a/packages/teleport-plugin-react-jss/src/index.ts
+++ b/packages/teleport-plugin-react-jss/src/index.ts
@@ -93,7 +93,11 @@ export const createReactJSSPlugin: ComponentPluginFactory<JSSConfig> = (config)
 
     const exportChunk = chunks.find((chunk) => chunk.name === exportChunkName)
 
-    const exportStatement = ASTBuilders.createReactJSSDefaultExport(uidl.name, jssDeclarationName)
+    const componentName = UIDLUtils.getComponentClassName(uidl)
+    const exportStatement = ASTBuilders.createReactJSSDefaultExport(
+      componentName,
+      jssDeclarationName
+    )
 
     if (exportChunk) {
       exportChunk.content = exportStatement
diff --git a/packages/teleport-plugin-stencil-app-routing/src/utils.ts b/packages/teleport-plugin-stencil-app-routing/src/utils.ts
index 6aedb1d1..1893ea6a 100644
--- a/packages/teleport-plugin-stencil-app-routing/src/utils.ts
+++ b/packages/teleport-plugin-stencil-app-routing/src/utils.ts
@@ -1,5 +1,5 @@
 import * as types from '@babel/types'
-import { ASTBuilders, ASTUtils, UIDLUtils, StringUtils } from '@teleporthq/teleport-shared'
+import { ASTBuilders, ASTUtils, StringUtils } from '@teleporthq/teleport-shared'
 import { UIDLStateDefinition, UIDLConditionalNode } from '@teleporthq/teleport-types'
 
 export const createClassDeclaration = (
@@ -13,7 +13,8 @@ export const createClassDeclaration = (
 
   routes.forEach((routeNode) => {
     const pageKey = routeNode.content.value.toString()
-    const { componentName, navLink } = UIDLUtils.extractPageOptions(routeDefinitions, pageKey)
+    const pageDefinition = routeDefinitions.values.find((route) => route.value === pageKey)
+    const { componentName, navLink } = pageDefinition.pageOptions
 
     const stencilRouteTag = ASTBuilders.createJSXTag('stencil-route')
     ASTUtils.addAttributeToJSXTag(stencilRouteTag, 'url', navLink)
diff --git a/packages/teleport-plugin-stencil-base-component/src/index.ts b/packages/teleport-plugin-stencil-base-component/src/index.ts
index a7116489..abde0870 100644
--- a/packages/teleport-plugin-stencil-base-component/src/index.ts
+++ b/packages/teleport-plugin-stencil-base-component/src/index.ts
@@ -5,6 +5,7 @@ import {
   ASTBuilders,
   StringUtils,
   ASTUtils,
+  UIDLUtils,
 } from '@teleporthq/teleport-shared'
 import {
   ComponentPluginFactory,
@@ -81,15 +82,16 @@ export const createStencilComponentPlugin: ComponentPluginFactory<StencilPluginC
       jsxTagStructure.children.unshift(titleAST)
     }
 
+    const componentName = UIDLUtils.getComponentClassName(uidl)
     const exportAST = createClassDeclaration(
-      uidl.name,
+      componentName,
       propDefinitions,
       stateDefinitions,
       jsxTagStructure
     )
 
     const params = {
-      tag: `app-${StringUtils.camelCaseToDashCase(uidl.name)}`,
+      tag: `app-${StringUtils.camelCaseToDashCase(componentName)}`,
       shadow: true,
     }
 
diff --git a/packages/teleport-plugin-vue-app-routing/src/index.ts b/packages/teleport-plugin-vue-app-routing/src/index.ts
index 2b085fc8..de7e2e5c 100644
--- a/packages/teleport-plugin-vue-app-routing/src/index.ts
+++ b/packages/teleport-plugin-vue-app-routing/src/index.ts
@@ -5,6 +5,7 @@ import {
   ComponentPlugin,
   ChunkType,
   FileType,
+  UIDLPageOptions,
 } from '@teleporthq/teleport-types'
 
 interface VueRouterConfig {
@@ -40,7 +41,7 @@ export const createVueAppRoutingPlugin: ComponentPluginFactory<VueRouterConfig>
     )
 
     const routes = UIDLUtils.extractRoutes(uidl)
-    const routeDefinitions = uidl.stateDefinitions.route
+    const routeValues = uidl.stateDefinitions.route.values || []
     const pageDependencyPrefix = options.localDependenciesPrefix || './'
 
     /* If pages are exported in their own folder and in custom file names.
@@ -51,16 +52,14 @@ export const createVueAppRoutingPlugin: ComponentPluginFactory<VueRouterConfig>
          so the `/component` suffix is computed below.
       */
     const pageStrategyOptions = (options.strategy && options.strategy.pages.options) || {}
-    const pageComponentSuffix = pageStrategyOptions.createFolderForEachComponent
-      ? '/' + (pageStrategyOptions.customComponentFileName || 'index')
-      : ''
+    const pageComponentSuffix = pageStrategyOptions.createFolderForEachComponent ? '/index' : ''
 
     const routesAST = routes.map((routeNode) => {
       const pageKey = routeNode.content.value.toString()
-      const { fileName, componentName, navLink } = UIDLUtils.extractPageOptions(
-        routeDefinitions,
-        pageKey
-      )
+
+      const pageDefinition = routeValues.find((route) => route.value === pageKey)
+      const defaultOptions: UIDLPageOptions = {}
+      const { componentName, navLink, fileName } = pageDefinition.pageOptions || defaultOptions
 
       dependencies[componentName] = {
         type: 'local',
diff --git a/packages/teleport-plugin-vue-base-component/src/utils.ts b/packages/teleport-plugin-vue-base-component/src/utils.ts
index 67aa4bf6..98a9ba08 100644
--- a/packages/teleport-plugin-vue-base-component/src/utils.ts
+++ b/packages/teleport-plugin-vue-base-component/src/utils.ts
@@ -1,6 +1,6 @@
 import * as types from '@babel/types'
 
-import { ASTUtils, ASTBuilders, ParsedASTNode } from '@teleporthq/teleport-shared'
+import { ASTUtils, ASTBuilders, ParsedASTNode, UIDLUtils } from '@teleporthq/teleport-shared'
 import {
   UIDLPropDefinition,
   UIDLStateDefinition,
@@ -63,9 +63,11 @@ export const generateVueComponentJS = (
     )
   }
 
+  const componentName = UIDLUtils.getComponentClassName(uidl)
+
   return t.exportDefaultDeclaration(
     t.objectExpression([
-      t.objectProperty(t.identifier('name'), t.stringLiteral(uidl.name)),
+      t.objectProperty(t.identifier('name'), t.stringLiteral(componentName)),
       ...vueObjectProperties,
     ])
   )
diff --git a/packages/teleport-project-generator/src/file-handlers.ts b/packages/teleport-project-generator/src/file-handlers.ts
index a7f60952..e01083a3 100644
--- a/packages/teleport-project-generator/src/file-handlers.ts
+++ b/packages/teleport-project-generator/src/file-handlers.ts
@@ -44,12 +44,11 @@ export const createComponentModule = async (uidl: ProjectUIDL, strategy: Project
     path,
     strategy.components.path
   )
-  const moduleComponents = Object.keys(uidl.components)
 
   const options = {
     localDependenciesPrefix: componentLocalDependenciesPrefix,
     strategy,
-    moduleComponents,
+    moduleComponents: uidl.components,
   }
 
   root.outputOptions = root.outputOptions || {}
@@ -65,7 +64,6 @@ export const createPageModule = async (
   options: GeneratorOptions
 ) => {
   pageUIDL.outputOptions = pageUIDL.outputOptions || {}
-  pageUIDL.outputOptions.fileName = pageUIDL.outputOptions.folderPath[0]
   pageUIDL.outputOptions.moduleName = `${StringUtils.dashCaseToUpperCamelCase(
     pageUIDL.outputOptions.folderPath[0]
   )}Module`
diff --git a/packages/teleport-project-generator/src/index.ts b/packages/teleport-project-generator/src/index.ts
index 6b6914cc..61c74087 100644
--- a/packages/teleport-project-generator/src/index.ts
+++ b/packages/teleport-project-generator/src/index.ts
@@ -161,15 +161,13 @@ export class ProjectGenerator {
       const { files, dependencies } = await createPage(pageUIDL, this.strategy, options)
 
       // Pages might be generated inside subfolders in the main pages folder
-      const relativePath = UIDLUtils.getComponentPath(pageUIDL)
+      const relativePath = UIDLUtils.getComponentFolderPath(pageUIDL)
       const path = this.strategy.pages.path.concat(relativePath)
 
       injectFilesToPath(rootFolder, path, files)
       collectedDependencies = { ...collectedDependencies, ...dependencies }
 
-      const { moduleGenerator } = this.strategy.pages
-
-      if (moduleGenerator) {
+      if (this.strategy.pages.moduleGenerator) {
         const pageModule = await createPageModule(pageUIDL, this.strategy, options)
         injectFilesToPath(rootFolder, path, pageModule.files)
       }
@@ -181,7 +179,7 @@ export class ProjectGenerator {
       const { files, dependencies } = await createComponent(componentUIDL, this.strategy, options)
 
       // Components might be generated inside subfolders in the main components folder
-      const relativePath = UIDLUtils.getComponentPath(componentUIDL)
+      const relativePath = UIDLUtils.getComponentFolderPath(componentUIDL)
       const path = this.strategy.components.path.concat(relativePath)
 
       injectFilesToPath(rootFolder, path, files)
diff --git a/packages/teleport-project-generator/src/utils.ts b/packages/teleport-project-generator/src/utils.ts
index f69407b7..a3d1357a 100644
--- a/packages/teleport-project-generator/src/utils.ts
+++ b/packages/teleport-project-generator/src/utils.ts
@@ -1,4 +1,4 @@
-import { UIDLUtils } from '@teleporthq/teleport-shared'
+import { UIDLUtils, StringUtils } from '@teleporthq/teleport-shared'
 
 import {
   GeneratedFile,
@@ -8,6 +8,9 @@ import {
   ProjectUIDL,
   UIDLConditionalNode,
   ProjectStrategy,
+  UIDLStateDefinition,
+  UIDLPageOptions,
+  UIDLComponentOutputOptions,
 } from '@teleporthq/teleport-types'
 import { elementNode } from '@teleporthq/teleport-uidl-builders'
 
@@ -26,12 +29,18 @@ const createPageUIDL = (
   const routeDefinition = uidl.root.stateDefinitions.route
   const pagesStrategyOptions = strategy.pages.options || {}
 
-  const { componentName, fileName } = UIDLUtils.extractPageOptions(
+  const pageOptions = extractPageOptions(
     routeDefinition,
     pageName,
     pagesStrategyOptions.useFileNameForNavigation
   )
 
+  // Update pageOptions based on the values computed at the previous step
+  const pageDefinition = routeDefinition.values.find((route) => route.value === pageName)
+  pageDefinition.pageOptions = pageOptions
+
+  const { fileName, componentName } = pageOptions
+
   // If the file name will not be used as the path (eg: next, nuxt)
   // And if the option to create each page in its folder is passed (eg: preact)
   const createFolderForEachComponent =
@@ -44,8 +53,10 @@ const createPageUIDL = (
     customTemplateFileName,
   } = pagesStrategyOptions
 
+  // a page can be: 'about-us.js' or `about-us/index.js`
   const outputOptions = createFolderForEachComponent
     ? {
+        componentName,
         fileName: (customComponentFileName && customComponentFileName(fileName)) || 'index',
         styleFileName: (customStyleFileName && customStyleFileName(fileName)) || 'style',
         templateFileName:
@@ -53,15 +64,15 @@ const createPageUIDL = (
         folderPath: [fileName],
       }
     : {
-        fileName,
-        styleFileName: fileName,
-        templateFileName: fileName,
+        componentName,
+        fileName: (customComponentFileName && customComponentFileName(fileName)) || fileName,
+        styleFileName: (customStyleFileName && customStyleFileName(fileName)) || fileName,
+        templateFileName: (customTemplateFileName && customTemplateFileName(fileName)) || fileName,
         folderPath: [],
       }
 
   // Looking into the state definition, we take the seo information for the corresponding page
   // If no title is provided for the page, the global settings title is passed as a default
-  const pageDefinition = routeDefinition.values.find((stateDef) => stateDef.value === pageName)
   const title = (pageDefinition.seo && pageDefinition.seo.title) || uidl.globals.settings.title
   const seo = {
     ...pageDefinition.seo,
@@ -81,6 +92,53 @@ const createPageUIDL = (
   }
 }
 
+/**
+ * A couple of different cases which need to be handled
+ * In case of next/nuxt generators, the file names represent the urls of the pages
+ * Also the root path needs to be represented by the index file
+ */
+export const extractPageOptions = (
+  routeDefinitions: UIDLStateDefinition,
+  routeName: string,
+  useFileNameForNavigation = false
+): UIDLPageOptions => {
+  const isHomePage = routeDefinitions.defaultValue === routeName
+  const pageDefinitions = routeDefinitions.values || []
+  const pageDefinition = pageDefinitions.find((stateDef) => stateDef.value === routeName)
+
+  // If no meta object is defined, the stateName is used
+  const friendlyStateName = StringUtils.removeIllegalCharacters(routeName) // remove space, leading numbers, etc.
+  const friendlyComponentName = StringUtils.dashCaseToUpperCamelCase(friendlyStateName) // component name in UpperCamelCase
+  const friendlyFileName = StringUtils.camelCaseToDashCase(friendlyStateName) // file name in dash-case
+
+  let pageOptions: UIDLPageOptions = {
+    // default values extracted from state name
+    fileName: friendlyFileName,
+    componentName: friendlyComponentName,
+    navLink: '/' + (isHomePage ? '' : friendlyFileName),
+  }
+
+  if (pageDefinition && pageDefinition.pageOptions) {
+    // The pageDefinition values have precedence, defaults are fallbacks
+    pageOptions = {
+      ...pageOptions,
+      ...pageDefinition.pageOptions,
+    }
+  }
+
+  // In case of next/nuxt, the path dictates the file name, so this is adjusted accordingly
+  // Also, the defaultPage has to be index, overriding any other value set
+  if (useFileNameForNavigation) {
+    const fileName = pageOptions.navLink.replace('/', '')
+    pageOptions.fileName = isHomePage ? 'index' : fileName
+  }
+
+  const otherPages = pageDefinitions.filter((page) => page.value !== routeName && page.pageOptions)
+  deduplicatePageOptionValues(pageOptions, otherPages.map((page) => page.pageOptions))
+
+  return pageOptions
+}
+
 export const prepareComponentOutputOptions = (
   components: Record<string, ComponentUIDL>,
   strategy: ProjectStrategy
@@ -89,43 +147,161 @@ export const prepareComponentOutputOptions = (
 
   Object.keys(components).forEach((componentKey) => {
     const component = components[componentKey]
-    const fileName = UIDLUtils.getComponentFileName(component)
-    const folderPath = UIDLUtils.getComponentPath(component)
+
+    // values coming from the input UIDL
+    const { fileName, componentClassName } = component.outputOptions || {
+      fileName: '',
+      componentClassName: '',
+    }
+
+    const friendlyName = StringUtils.removeIllegalCharacters(component.name)
+    const friendlyFileName = fileName || StringUtils.camelCaseToDashCase(friendlyName) // ex: primary-button
+    const friendlyComponentName =
+      componentClassName || StringUtils.dashCaseToUpperCamelCase(friendlyName) // ex: PrimaryButton
+    const folderPath = UIDLUtils.getComponentFolderPath(component)
+
+    const {
+      customComponentFileName,
+      customStyleFileName,
+      customTemplateFileName,
+    } = componentStrategyOptions
 
     // If the component has its own folder, name is 'index' or an override from the strategy.
     // In this case, the file name (dash converted) is used as the folder name
     if (componentStrategyOptions.createFolderForEachComponent) {
-      const {
-        customComponentFileName,
-        customStyleFileName,
-        customTemplateFileName,
-      } = componentStrategyOptions
-
       component.outputOptions = {
-        fileName: (customComponentFileName && customComponentFileName(fileName)) || 'index',
-        styleFileName: (customStyleFileName && customStyleFileName(fileName)) || 'style',
+        componentClassName: friendlyComponentName,
+        fileName: (customComponentFileName && customComponentFileName(friendlyFileName)) || 'index',
+        styleFileName: (customStyleFileName && customStyleFileName(friendlyFileName)) || 'style',
         templateFileName:
-          (customTemplateFileName && customTemplateFileName(fileName)) || 'template',
-        folderPath: [...folderPath, fileName],
+          (customTemplateFileName && customTemplateFileName(friendlyFileName)) || 'template',
+        folderPath: [...folderPath, friendlyFileName],
       }
     } else {
       component.outputOptions = {
-        fileName,
-        styleFileName: fileName,
-        templateFileName: fileName,
+        componentClassName: friendlyComponentName,
+        fileName:
+          (customComponentFileName && customComponentFileName(friendlyFileName)) ||
+          friendlyFileName,
+        styleFileName:
+          (customStyleFileName && customStyleFileName(friendlyFileName)) || friendlyFileName,
+        templateFileName:
+          (customTemplateFileName && customTemplateFileName(friendlyFileName)) || friendlyFileName,
         folderPath,
       }
     }
+
+    const otherComponents = Object.keys(components).filter(
+      (key) => key !== componentKey && components[key].outputOptions
+    )
+    deduplicateComponentOutputOptions(
+      component.outputOptions,
+      otherComponents.map((key) => components[key].outputOptions)
+    )
   })
 }
 
+const deduplicatePageOptionValues = (options: UIDLPageOptions, otherOptions: UIDLPageOptions[]) => {
+  let navlinkSuffix = 0
+  while (otherOptions.some((opt) => opt.navLink === appendSuffix(options.navLink, navlinkSuffix))) {
+    navlinkSuffix++
+  }
+
+  if (navlinkSuffix > 0) {
+    options.navLink = appendSuffix(options.navLink, navlinkSuffix)
+    console.warn(
+      `Potential duplication solved by appending '${navlinkSuffix}' to the navlink: ${options.navLink}`
+    )
+  }
+
+  let componentNameSuffix = 0
+  while (
+    otherOptions.some(
+      (opt) => opt.componentName === appendSuffix(options.componentName, componentNameSuffix)
+    )
+  ) {
+    componentNameSuffix++
+  }
+
+  if (componentNameSuffix > 0) {
+    options.componentName = appendSuffix(options.componentName, componentNameSuffix)
+    console.warn(
+      `Potential duplication solved by appending '${componentNameSuffix}' to the componentName: ${options.componentName}`
+    )
+  }
+
+  let fileNameSuffix = 0
+  while (
+    otherOptions.some((opt) => opt.fileName === appendSuffix(options.fileName, fileNameSuffix))
+  ) {
+    fileNameSuffix++
+  }
+
+  if (fileNameSuffix > 0) {
+    options.fileName = appendSuffix(options.fileName, fileNameSuffix)
+    console.warn(
+      `Potential duplication solved by appending '${fileNameSuffix}' to the fileName: ${options.fileName}`
+    )
+  }
+}
+
+const deduplicateComponentOutputOptions = (
+  options: UIDLComponentOutputOptions,
+  otherOptions: UIDLComponentOutputOptions[]
+) => {
+  let componentNameSuffix = 0
+  while (
+    otherOptions.some(
+      (opt) =>
+        opt.componentClassName === appendSuffix(options.componentClassName, componentNameSuffix) &&
+        equalPaths(opt.folderPath, options.folderPath)
+    )
+  ) {
+    componentNameSuffix++
+  }
+
+  if (componentNameSuffix > 0) {
+    options.componentClassName = appendSuffix(options.componentClassName, componentNameSuffix)
+    console.warn(
+      `Potential duplication solved by appending a '${componentNameSuffix}' to the component class name: ${options.componentClassName}`
+    )
+  }
+
+  let fileNameSuffix = 0
+  while (
+    otherOptions.some(
+      (opt) =>
+        opt.fileName === appendSuffix(options.fileName, fileNameSuffix) &&
+        equalPaths(opt.folderPath, options.folderPath)
+    )
+  ) {
+    fileNameSuffix++
+  }
+
+  if (fileNameSuffix > 0) {
+    options.fileName = appendSuffix(options.fileName, fileNameSuffix)
+    console.warn(
+      `Potential duplication solved by appending a '${fileNameSuffix}' to the file name: ${options.fileName}`
+    )
+  }
+}
+
+const appendSuffix = (str: string, suffix: number) => {
+  const stringSuffix = suffix === 0 ? '' : suffix.toString()
+  return str + stringSuffix
+}
+
+const equalPaths = (path1: string[], path2: string[]) => {
+  return JSON.stringify(path1) === JSON.stringify(path2)
+}
+
 export const resolveLocalDependencies = (
   pageUIDLs: ComponentUIDL[],
   components: Record<string, ComponentUIDL>,
   strategy: ProjectStrategy
 ) => {
   pageUIDLs.forEach((pageUIDL) => {
-    const pagePath = UIDLUtils.getComponentPath(pageUIDL)
+    const pagePath = UIDLUtils.getComponentFolderPath(pageUIDL)
     const fromPath = strategy.pages.path.concat(pagePath)
     UIDLUtils.traverseElements(pageUIDL.node, (element) => {
       if (isLocalDependency(element)) {
@@ -136,7 +312,7 @@ export const resolveLocalDependencies = (
 
   Object.keys(components).forEach((componentKey) => {
     const component = components[componentKey]
-    const componentPath = UIDLUtils.getComponentPath(component)
+    const componentPath = UIDLUtils.getComponentFolderPath(component)
     const fromPath = strategy.components.path.concat(componentPath)
 
     UIDLUtils.traverseElements(component.node, (element) => {
@@ -158,13 +334,15 @@ const setLocalDependencyPath = (
 ) => {
   const componentKey = element.elementType
   const component = components[componentKey]
-  const componentPath = UIDLUtils.getComponentPath(component)
+  const componentPath = UIDLUtils.getComponentFolderPath(component)
+  const componentClassName = UIDLUtils.getComponentClassName(component)
 
   const toPath = toBasePath.concat(componentPath)
 
   const importFileName = UIDLUtils.getComponentFileName(component)
   const importPath = generateLocalDependenciesPrefix(fromPath, toPath)
   element.dependency.path = `${importPath}${importFileName}`
+  element.elementType = componentClassName
 }
 
 export const generateLocalDependenciesPrefix = (fromPath: string[], toPath: string[]): string => {
diff --git a/packages/teleport-shared/src/node-handlers/node-to-html/index.ts b/packages/teleport-shared/src/node-handlers/node-to-html/index.ts
index 59d6d153..e5ea77ad 100644
--- a/packages/teleport-shared/src/node-handlers/node-to-html/index.ts
+++ b/packages/teleport-shared/src/node-handlers/node-to-html/index.ts
@@ -12,14 +12,13 @@ import {
 import { createConditionalStatement, handleAttribute, handleEvent } from './utils'
 import { NodeToHTML } from './types'
 import { DEFAULT_TEMPLATE_SYNTAX } from './constants'
-import { camelCaseToDashCase } from '../../utils/string-utils'
 
 const generateElementNode: NodeToHTML<UIDLElementNode, HastNode> = (node, params, syntax) => {
   const templateSyntax = { ...DEFAULT_TEMPLATE_SYNTAX, ...syntax }
   const { dependencies, templateLookup } = params
   const { elementType, name, key, children, attrs, dependency, events } = node.content
   const htmlNode = dependency
-    ? createHTMLNode(templateSyntax.customElementTagName(camelCaseToDashCase(elementType)))
+    ? createHTMLNode(templateSyntax.customElementTagName(elementType))
     : createHTMLNode(elementType)
 
   if (dependency && templateSyntax.dependencyHandling === 'import') {
diff --git a/packages/teleport-shared/src/utils/string-utils.ts b/packages/teleport-shared/src/utils/string-utils.ts
index c1506074..095241d8 100644
--- a/packages/teleport-shared/src/utils/string-utils.ts
+++ b/packages/teleport-shared/src/utils/string-utils.ts
@@ -8,8 +8,18 @@ export const capitalize = (str: string): string => str[0].toUpperCase() + str.sl
 
 export const dashCaseToUpperCamelCase = (str: string) => capitalize(dashCaseToCamelCase(str))
 
-// Replaces all ocurrences of non alpha-numeric characters in the string (except _)
-export const sanitizeVariableName = (str: string): string => str.replace(/\W/g, '')
+export const removeIllegalCharacters = (str: string) => {
+  if (typeof str !== 'string') {
+    return null
+  }
+
+  return str
+    .replace(/[^a-zA-Z0-9-_]/g, '') // Remove all non-alphanumeric characters except _ and -
+    .replace(/^[0-9-_]*/, '') // Remove leading numbers
+    .replace(/\-\-+/g, '-') // Replace multiple - with single -
+    .replace(/^-+/, '') // Trim - from start of text
+    .replace(/-+$/, '') // Trim - from end of text
+}
 
 export const slugify = (str: string): string => {
   if (str == null) {
diff --git a/packages/teleport-shared/src/utils/uidl-utils.ts b/packages/teleport-shared/src/utils/uidl-utils.ts
index 1bc2988d..ad628b88 100644
--- a/packages/teleport-shared/src/utils/uidl-utils.ts
+++ b/packages/teleport-shared/src/utils/uidl-utils.ts
@@ -1,8 +1,7 @@
 import { ASSETS_IDENTIFIER } from '../constants'
-import { camelCaseToDashCase, dashCaseToUpperCamelCase } from './string-utils'
+import { camelCaseToDashCase } from './string-utils'
 import {
   ComponentUIDL,
-  UIDLStateDefinition,
   UIDLStyleDefinitions,
   UIDLConditionalNode,
   UIDLElement,
@@ -12,51 +11,9 @@ import {
   UIDLDynamicReference,
   UIDLRepeatContent,
   UIDLRepeatMeta,
-  UIDLPageOptions,
   UIDLElementNode,
 } from '@teleporthq/teleport-types'
 
-/**
- * A couple of different cases which need to be handled
- * In case of next/nuxt generators, the file names represent the urls of the pages
- * Also the root path needs to be represented by the index file
- */
-export const extractPageOptions = (
-  routeDefinitions: UIDLStateDefinition,
-  stateName: string,
-  useFileNameForNavigation = false
-): UIDLPageOptions => {
-  const defaultPage = routeDefinitions.defaultValue
-  const pageDefinitions = routeDefinitions.values || []
-  const pageDefinition = pageDefinitions.find((stateDef) => stateDef.value === stateName)
-
-  // If no meta object is defined, the stateName is used
-  const defaultPageMetadata: UIDLPageOptions = {
-    fileName: useFileNameForNavigation && stateName === defaultPage ? 'index' : stateName,
-    componentName: dashCaseToUpperCamelCase(stateName),
-    navLink: '/' + stateName,
-  }
-
-  if (!pageDefinition || !pageDefinition.pageOptions) {
-    return defaultPageMetadata
-  }
-
-  // The pageDefinition values have precedence, defaults are fallbacks
-  const pageMetadata = {
-    ...defaultPageMetadata,
-    ...pageDefinition.pageOptions,
-  }
-
-  // In case of next/nuxt, the path dictates the file name, so this is adjusted accordingly
-  // Also, the defaultPage has to be index, overriding any other value set
-  if (useFileNameForNavigation) {
-    const fileName = pageMetadata.navLink.replace('/', '')
-    pageMetadata.fileName = stateName === defaultPage ? 'index' : fileName
-  }
-
-  return pageMetadata
-}
-
 export const extractRoutes = (rootComponent: ComponentUIDL) => {
   // Assuming root element starts with a UIDLElementNode
   const rootElement = rootComponent.node.content as UIDLElement
@@ -68,11 +25,9 @@ export const extractRoutes = (rootComponent: ComponentUIDL) => {
 }
 
 export const getComponentFileName = (component: ComponentUIDL) => {
-  const name =
-    component.outputOptions && component.outputOptions.fileName
-      ? component.outputOptions.fileName
-      : component.name
-  return camelCaseToDashCase(name)
+  return component.outputOptions && component.outputOptions.fileName
+    ? component.outputOptions.fileName
+    : camelCaseToDashCase(getComponentClassName(component))
 }
 
 export const getStyleFileName = (component: ComponentUIDL) => {
@@ -93,8 +48,15 @@ export const getTemplateFileName = (component: ComponentUIDL) => {
     : componentFileName
 }
 
-export const getComponentPath = (component: ComponentUIDL) =>
-  component.outputOptions ? component.outputOptions.folderPath : []
+export const getComponentFolderPath = (component: ComponentUIDL) =>
+  component.outputOptions && component.outputOptions.folderPath
+    ? component.outputOptions.folderPath
+    : []
+
+export const getComponentClassName = (component: ComponentUIDL) =>
+  component.outputOptions && component.outputOptions.componentClassName
+    ? component.outputOptions.componentClassName
+    : component.name
 
 export const getRepeatIteratorNameAndKey = (meta: UIDLRepeatMeta = {}) => {
   const iteratorName = meta.iteratorName || 'item'
diff --git a/packages/teleport-types/src/generators.ts b/packages/teleport-types/src/generators.ts
index 0819b9ba..3d9a6fb5 100644
--- a/packages/teleport-types/src/generators.ts
+++ b/packages/teleport-types/src/generators.ts
@@ -86,7 +86,7 @@ export interface GeneratorOptions {
   skipValidation?: boolean
   projectRouteDefinition?: UIDLStateDefinition
   strategy?: ProjectStrategy
-  moduleComponents?: string[]
+  moduleComponents?: Record<string, ComponentUIDL>
 }
 
 export type CodeGeneratorFunction<T> = (content: T) => string
diff --git a/packages/teleport-types/src/uidl.ts b/packages/teleport-types/src/uidl.ts
index 4fc50120..d5e66f4a 100755
--- a/packages/teleport-types/src/uidl.ts
+++ b/packages/teleport-types/src/uidl.ts
@@ -36,16 +36,19 @@ export interface ComponentUIDL {
   node: UIDLElementNode
   propDefinitions?: Record<string, UIDLPropDefinition>
   stateDefinitions?: Record<string, UIDLStateDefinition>
-  outputOptions?: {
-    fileName?: string
-    styleFileName?: string
-    templateFileName?: string
-    moduleName?: string
-    folderPath?: string[]
-  }
+  outputOptions?: UIDLComponentOutputOptions
   seo?: UIDLComponentSEO
 }
 
+export interface UIDLComponentOutputOptions {
+  componentClassName?: string // needs to be a valid class name
+  fileName?: string // needs to be a valid file name
+  styleFileName?: string
+  templateFileName?: string
+  moduleName?: string
+  folderPath?: string[]
+}
+
 export interface UIDLComponentSEO {
   title?: string
   metaTags?: UIDLMetaTag[]
@@ -63,11 +66,13 @@ export interface UIDLPropDefinition {
 export interface UIDLStateDefinition {
   type: string
   defaultValue: string | number | boolean | any[] | object | (() => void)
-  values?: Array<{
-    value: string | number | boolean
-    pageOptions?: UIDLPageOptions // Used when the StateDefinition is used as the router
-    seo?: UIDLComponentSEO
-  }>
+  values?: UIDLStateValueDetails[]
+}
+
+export interface UIDLStateValueDetails {
+  value: string | number | boolean
+  pageOptions?: UIDLPageOptions // Used when the StateDefinition is used as the router
+  seo?: UIDLComponentSEO
 }
 
 export interface UIDLPageOptions {
diff --git a/packages/teleport-uidl-resolver/src/resolver.ts b/packages/teleport-uidl-resolver/src/resolver.ts
index 7bd0741d..76f29f10 100644
--- a/packages/teleport-uidl-resolver/src/resolver.ts
+++ b/packages/teleport-uidl-resolver/src/resolver.ts
@@ -1,5 +1,5 @@
 import * as utils from './utils'
-import { StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
+import { UIDLUtils, StringUtils } from '@teleporthq/teleport-shared'
 import { ComponentUIDL, UIDLElement, Mapping, GeneratorOptions } from '@teleporthq/teleport-types'
 
 /**
@@ -33,6 +33,16 @@ export default class Resolver {
 
     const node = UIDLUtils.cloneObject(uidl.node)
 
+    uidl.outputOptions = uidl.outputOptions || {}
+    const friendlyName = StringUtils.removeIllegalCharacters(uidl.name)
+    if (!uidl.outputOptions.fileName) {
+      uidl.outputOptions.fileName = StringUtils.camelCaseToDashCase(friendlyName)
+    }
+
+    if (!uidl.outputOptions.componentClassName) {
+      uidl.outputOptions.componentClassName = StringUtils.dashCaseToUpperCamelCase(friendlyName)
+    }
+
     if (options.projectRouteDefinition) {
       utils.resolveNavlinks(node, options.projectRouteDefinition)
     }
@@ -45,14 +55,11 @@ export default class Resolver {
 
     utils.ensureDataSourceUniqueness(node)
 
-    const name = StringUtils.sanitizeVariableName(uidl.name)
-
     // There might be urls that need to be prefixed in the metaTags of the component
     utils.resolveMetaTags(uidl, options)
 
     return {
       ...uidl,
-      name,
       node,
     }
   }
diff --git a/packages/teleport-uidl-resolver/src/utils.ts b/packages/teleport-uidl-resolver/src/utils.ts
index e542ecfe..5e8da0e8 100644
--- a/packages/teleport-uidl-resolver/src/utils.ts
+++ b/packages/teleport-uidl-resolver/src/utils.ts
@@ -60,6 +60,16 @@ export const resolveNavlinks = (uidlNode: UIDLNode, routesDefinition: UIDLStateD
         return
       }
 
+      if (!transitionRoute) {
+        throw new Error(
+          `Invalid route value: '${transitionState}' was specified as transitionTo attribute on element\n ${JSON.stringify(
+            element,
+            null,
+            2
+          )}`
+        )
+      }
+
       if (transitionRoute && transitionRoute.pageOptions && transitionRoute.pageOptions.navLink) {
         transitionAttribute.content = transitionRoute.pageOptions.navLink
       } else {
@@ -431,6 +441,7 @@ const resolveDependency = (
     // When a dependency is specified without a path, we infer it is a local import.
 
     // ex: PrimaryButton component should be written in a file called primary-button
+    // This is just a fallback for when the dependency path is not set by a project generator
     const componentName = mappedElement.elementType
     const componentFileName = StringUtils.camelCaseToDashCase(componentName)
 
diff --git a/packages/teleport-uidl-validator/src/uidl-schemas/component.json b/packages/teleport-uidl-validator/src/uidl-schemas/component.json
index 9b2b48cb..a6ea38c0 100644
--- a/packages/teleport-uidl-validator/src/uidl-schemas/component.json
+++ b/packages/teleport-uidl-validator/src/uidl-schemas/component.json
@@ -283,7 +283,7 @@
       "additionalProperties": false,
       "properties": {
         "additionalProperties": false,
-        "elementType": {"type": "string", "pattern": "^([a-zA-Z0-9])*$"},
+        "elementType": {"type": "string" },
         "name": {"type": "string"},
         "key": {"type": "string"},
         "dependency": {"$ref": "#/definitions/componentDependency"},
diff --git a/packages/teleport-uidl-validator/src/validator/utils.ts b/packages/teleport-uidl-validator/src/validator/utils.ts
index 781b1319..26c404ab 100644
--- a/packages/teleport-uidl-validator/src/validator/utils.ts
+++ b/packages/teleport-uidl-validator/src/validator/utils.ts
@@ -197,7 +197,7 @@ export const checkRootComponent = (input: ProjectUIDL) => {
     input.root.stateDefinitions.route.values
       .filter((route) => !routeNaming.includes(route.value.toString()))
       .forEach((route) => {
-        const errorMsg = `\nRoot Node contains a route that don't have a specified state: ${route.pageOptions.navLink}.`
+        const errorMsg = `\nRoot Node contains a route that don't have a specified state: ${route.value}.`
         errors.push(errorMsg)
       })
   }


Test patch:
diff --git a/examples/test-samples/project-sample.json b/examples/test-samples/project-sample.json
index 8a869a1d..35f58426 100644
--- a/examples/test-samples/project-sample.json
+++ b/examples/test-samples/project-sample.json
@@ -85,6 +85,7 @@
             "value": "index",
             "pageOptions": {
               "navLink": "/",
+              "fileName": "index",
               "componentName": "Home"
             }
           },
@@ -92,6 +93,7 @@
             "value": "about",
             "pageOptions": {
               "navLink": "/about",
+              "fileName": "about",
               "componentName": "About"
             }
           },
@@ -99,6 +101,7 @@
             "value": "contact-us",
             "pageOptions": {
               "navLink": "/here-we-are",
+              "fileName": "contact-us",
               "componentName": "Us"
             }
           }
diff --git a/packages/teleport-plugin-angular-module/__tests__/index.ts b/packages/teleport-plugin-angular-module/__tests__/index.ts
index 65355494..fc3dd282 100644
--- a/packages/teleport-plugin-angular-module/__tests__/index.ts
+++ b/packages/teleport-plugin-angular-module/__tests__/index.ts
@@ -30,7 +30,7 @@ describe('Testing the functionality for Angular Modules', () => {
     const componentPlugin = createAngularModulePlugin({ moduleType: 'component' })
     const structure: ComponentStructure = {
       uidl: componentUIDL,
-      options: { moduleComponents: ['OneComponent'] },
+      options: { moduleComponents: { OneComponent: componentUIDL } },
       chunks: [],
       dependencies: {},
     }
diff --git a/packages/teleport-plugin-react-app-routing/__tests__/index.ts b/packages/teleport-plugin-react-app-routing/__tests__/index.ts
index fcf7527a..4d1bff0e 100644
--- a/packages/teleport-plugin-react-app-routing/__tests__/index.ts
+++ b/packages/teleport-plugin-react-app-routing/__tests__/index.ts
@@ -6,7 +6,7 @@ import {
   dynamicNode,
   definition,
 } from '@teleporthq/teleport-uidl-builders'
-import { ChunkType, ComponentStructure } from '@teleporthq/teleport-types'
+import { ChunkType, ComponentStructure, UIDLStateDefinition } from '@teleporthq/teleport-types'
 
 describe('plugin-react-app-routing', () => {
   const plugin = createReactAppRoutingPlugin({
@@ -15,6 +15,19 @@ describe('plugin-react-app-routing', () => {
   })
 
   it('outputs two AST chunks with the corresponding chunk names', async () => {
+    const routeDefinition: UIDLStateDefinition = definition('string', 'home')
+    routeDefinition.values = [
+      { value: 'home', pageOptions: { fileName: 'home', componentName: 'Home', navLink: '/' } },
+      {
+        value: 'about',
+        pageOptions: { fileName: 'about', componentName: 'About', navLink: '/about' },
+      },
+      {
+        value: 'contact',
+        pageOptions: { fileName: 'contact', componentName: 'Contact', navLink: '/contact' },
+      },
+    ]
+
     const structure: ComponentStructure = {
       chunks: [],
       options: {},
@@ -27,7 +40,7 @@ describe('plugin-react-app-routing', () => {
         ]),
         {},
         {
-          route: definition('string', 'home'),
+          route: routeDefinition,
         }
       ),
       dependencies: {},
diff --git a/packages/teleport-plugin-vue-app-routing/__tests__/index.ts b/packages/teleport-plugin-vue-app-routing/__tests__/index.ts
index f31af5c7..b9304459 100644
--- a/packages/teleport-plugin-vue-app-routing/__tests__/index.ts
+++ b/packages/teleport-plugin-vue-app-routing/__tests__/index.ts
@@ -6,7 +6,7 @@ import {
   dynamicNode,
   definition,
 } from '@teleporthq/teleport-uidl-builders'
-import { ComponentStructure, ChunkType } from '@teleporthq/teleport-types'
+import { ComponentStructure, ChunkType, UIDLStateDefinition } from '@teleporthq/teleport-types'
 
 describe('plugin-vue-app-routing', () => {
   const plugin = createVueAppRoutingPlugin({
@@ -14,6 +14,18 @@ describe('plugin-vue-app-routing', () => {
   })
 
   it('outputs three AST chunks with the corresponding chunk names', async () => {
+    const routeDefinition: UIDLStateDefinition = definition('string', 'home')
+    routeDefinition.values = [
+      { value: 'home', pageOptions: { fileName: 'home', componentName: 'Home', navLink: '/' } },
+      {
+        value: 'about',
+        pageOptions: { fileName: 'about', componentName: 'About', navLink: '/about' },
+      },
+      {
+        value: 'contact',
+        pageOptions: { fileName: 'contact', componentName: 'Contact', navLink: '/contact' },
+      },
+    ]
     const structure: ComponentStructure = {
       chunks: [],
       options: {},
@@ -26,7 +38,7 @@ describe('plugin-vue-app-routing', () => {
         ]),
         {},
         {
-          route: definition('string', 'home'),
+          route: routeDefinition,
         }
       ),
       dependencies: {},
diff --git a/packages/teleport-project-generator/__tests__/index.ts b/packages/teleport-project-generator/__tests__/index.ts
index 51deff3c..2a7ce19d 100644
--- a/packages/teleport-project-generator/__tests__/index.ts
+++ b/packages/teleport-project-generator/__tests__/index.ts
@@ -45,12 +45,11 @@ describe('Generic Project Generator', () => {
       expect(componentsGenerator.generateComponent).toBeCalledTimes(7)
       expect(componentsGenerator.generateComponent).toBeCalledWith(
         expect.objectContaining({ name: 'ExpandableArea' }),
-        {
+        expect.objectContaining({
           assetsPrefix: '/test/static',
-          projectRouteDefinition: uidl.root.stateDefinitions.route,
           mapping: {},
           skipValidation: true,
-        }
+        })
       )
       expect(entryGenerator.linkCodeChunks).toBeCalledTimes(1)
       expect(routerGenerator.generateComponent).toBeCalledTimes(1)
diff --git a/packages/teleport-project-generator/__tests__/utils.ts b/packages/teleport-project-generator/__tests__/utils.ts
index 44d70947..c28a476f 100644
--- a/packages/teleport-project-generator/__tests__/utils.ts
+++ b/packages/teleport-project-generator/__tests__/utils.ts
@@ -1,5 +1,12 @@
-import { generateLocalDependenciesPrefix, injectFilesToPath } from '../src/utils'
-import { emptyFolder, folderWithFiles } from './mocks'
+import {
+  generateLocalDependenciesPrefix,
+  injectFilesToPath,
+  extractPageOptions,
+  prepareComponentOutputOptions,
+} from '../src/utils'
+import { UIDLStateDefinition } from '@teleporthq/teleport-types'
+import { emptyFolder, folderWithFiles, createStrategyWithCommonGenerator } from './mocks'
+import { component, elementNode } from '@teleporthq/teleport-uidl-builders'
 
 describe('generateLocalDependenciesPrefix', () => {
   it('works when there is a common parent', () => {
@@ -135,3 +142,158 @@ describe('injectFilesToPath', () => {
     expect(folder.subFolders[0].files[1].content).toBe('<html>')
   })
 })
+
+describe('extractPageOptions', () => {
+  const routeDefinitions: UIDLStateDefinition = {
+    type: 'string',
+    defaultValue: 'home',
+    values: [
+      {
+        value: 'home',
+        pageOptions: {
+          navLink: '/',
+        },
+      },
+      {
+        value: 'about',
+        pageOptions: {
+          navLink: '/about-us',
+          componentName: 'AboutUs',
+        },
+      },
+      {
+        value: 'contact-us',
+        pageOptions: {
+          navLink: '/team',
+        },
+      },
+      {
+        value: 'no-meta',
+      },
+    ],
+  }
+
+  it('uses the state for a non-declared page', () => {
+    const result = extractPageOptions(routeDefinitions, 'non-declared')
+    expect(result.navLink).toBe('/non-declared')
+    expect(result.fileName).toBe('non-declared')
+    expect(result.componentName).toBe('NonDeclared')
+  })
+
+  it('uses the state for a page without meta', () => {
+    const result = extractPageOptions(routeDefinitions, 'no-meta')
+    expect(result.navLink).toBe('/no-meta')
+    expect(result.fileName).toBe('no-meta')
+    expect(result.componentName).toBe('NoMeta')
+  })
+
+  it('returns values from the meta with defaults from the state', () => {
+    const result = extractPageOptions(routeDefinitions, 'about')
+    expect(result.navLink).toBe('/about-us') // meta value
+    expect(result.fileName).toBe('about') // state value
+    expect(result.componentName).toBe('AboutUs') // meta value
+  })
+
+  it('converts the fileName to index', () => {
+    const result = extractPageOptions(routeDefinitions, 'home', true)
+    expect(result.navLink).toBe('/')
+    expect(result.fileName).toBe('index')
+    expect(result.componentName).toBe('Home')
+  })
+
+  it('uses the path as the fileName', () => {
+    const result = extractPageOptions(routeDefinitions, 'about', true)
+    expect(result.navLink).toBe('/about-us')
+    expect(result.fileName).toBe('about-us')
+    expect(result.componentName).toBe('AboutUs')
+  })
+})
+
+describe('prepareComponentOutputOptions', () => {
+  it('creates all output options based on the UIDL and the default conventions', () => {
+    const mockStrategy = createStrategyWithCommonGenerator()
+    const components = {
+      NavBar: component('NavBar', elementNode('container')),
+      'Primary Button': component('Primary Button', elementNode('container')),
+      'my-card': component('my-card', elementNode('container')),
+    }
+
+    prepareComponentOutputOptions(components, mockStrategy)
+
+    expect(components.NavBar.outputOptions.fileName).toBe('nav-bar')
+    expect(components.NavBar.outputOptions.componentClassName).toBe('NavBar')
+    expect(components['Primary Button'].outputOptions.fileName).toBe('primary-button')
+    expect(components['Primary Button'].outputOptions.componentClassName).toBe('PrimaryButton')
+    expect(components['my-card'].outputOptions.fileName).toBe('my-card')
+    expect(components['my-card'].outputOptions.componentClassName).toBe('MyCard')
+  })
+
+  it('works with components created in separate folders', () => {
+    const mockStrategy = createStrategyWithCommonGenerator()
+    mockStrategy.components.options = {
+      createFolderForEachComponent: true,
+      customStyleFileName: () => 'styling',
+    }
+    const components = {
+      NavBar: component('NavBar', elementNode('container')),
+      'Primary Button': component('Primary Button', elementNode('container')),
+      'my-card': component('my-card', elementNode('container')),
+    }
+
+    prepareComponentOutputOptions(components, mockStrategy)
+
+    expect(components.NavBar.outputOptions.folderPath[0]).toBe('nav-bar')
+    expect(components.NavBar.outputOptions.fileName).toBe('index')
+    expect(components.NavBar.outputOptions.componentClassName).toBe('NavBar')
+    expect(components.NavBar.outputOptions.styleFileName).toBe('styling')
+    expect(components['Primary Button'].outputOptions.folderPath[0]).toBe('primary-button')
+    expect(components['Primary Button'].outputOptions.fileName).toBe('index')
+    expect(components['Primary Button'].outputOptions.componentClassName).toBe('PrimaryButton')
+    expect(components['Primary Button'].outputOptions.styleFileName).toBe('styling')
+    expect(components['my-card'].outputOptions.folderPath[0]).toBe('my-card')
+    expect(components['my-card'].outputOptions.fileName).toBe('index')
+    expect(components['my-card'].outputOptions.componentClassName).toBe('MyCard')
+    expect(components['my-card'].outputOptions.styleFileName).toBe('styling')
+  })
+
+  it('uses the UIDL values', () => {
+    const mockStrategy = createStrategyWithCommonGenerator()
+    mockStrategy.components.options = {
+      createFolderForEachComponent: true,
+      customStyleFileName: () => 'styling',
+    }
+    const testComponent = component('NavBar', elementNode('container'))
+    testComponent.outputOptions = {
+      fileName: 'custom-filename',
+      folderPath: ['custom-folder'],
+    }
+    const components = {
+      testComponent,
+    }
+
+    prepareComponentOutputOptions(components, mockStrategy)
+
+    expect(testComponent.outputOptions.fileName).toBe('index')
+    expect(testComponent.outputOptions.folderPath[0]).toBe('custom-folder')
+    expect(testComponent.outputOptions.folderPath[1]).toBe('custom-filename')
+  })
+
+  it('deduplicates matching names', () => {
+    const mockStrategy = createStrategyWithCommonGenerator()
+    const components = {
+      'te sT': component('te sT', elementNode('container')),
+      'Tes t': component('Tes t', elementNode('container')),
+      test: component('test', elementNode('container')),
+    }
+
+    prepareComponentOutputOptions(components, mockStrategy)
+
+    expect(components['te sT'].outputOptions.fileName).toBe('tes-t')
+    expect(components['Tes t'].outputOptions.fileName).toBe('test')
+    expect(components.test.outputOptions.fileName).toBe('test1')
+
+    expect(components['te sT'].outputOptions.componentClassName).toBe('TesT')
+    expect(components['Tes t'].outputOptions.componentClassName).toBe('Test')
+    expect(components.test.outputOptions.componentClassName).toBe('Test1')
+  })
+})
diff --git a/packages/teleport-shared/__tests__/utils/string-utils.ts b/packages/teleport-shared/__tests__/utils/string-utils.ts
index 7d53dc92..412323e0 100644
--- a/packages/teleport-shared/__tests__/utils/string-utils.ts
+++ b/packages/teleport-shared/__tests__/utils/string-utils.ts
@@ -2,10 +2,10 @@ import {
   slugify,
   addSpacesToEachLine,
   removeLastEmptyLine,
-  sanitizeVariableName,
   camelCaseToDashCase,
   dashCaseToUpperCamelCase,
   dashCaseToCamelCase,
+  removeIllegalCharacters,
 } from '../../src/utils/string-utils'
 
 describe('slugify tests', () => {
@@ -86,13 +86,21 @@ describe('removeLastEmptyLine', () => {
   })
 })
 
-describe('sanitizeVariableName', () => {
-  it('does not change a valid string', () => {
-    expect(sanitizeVariableName('ComponentName')).toBe('ComponentName')
+describe('removeIllegalCharacters', () => {
+  it('removes leading numbers', () => {
+    expect(removeIllegalCharacters('123test')).toBe('test')
   })
 
-  it('removes spaces and other characters, but keeps underscore', () => {
-    expect(sanitizeVariableName('Component_ Name-')).toBe('Component_Name')
+  it('removes spaces and other characters', () => {
+    expect(removeIllegalCharacters("test 1  \\'.te^st=2")).toBe('test1test2')
+  })
+
+  it('returns the same string for dash case', () => {
+    expect(removeIllegalCharacters('about-page')).toBe('about-page')
+  })
+
+  it('returns the same string for camel case', () => {
+    expect(removeIllegalCharacters('AboutPage')).toBe('AboutPage')
   })
 })
 
@@ -113,6 +121,10 @@ describe('camelCaseToDashCase', () => {
     expect(camelCaseToDashCase('1prim12arybutton')).toBe('1prim12arybutton')
   })
 
+  it('ignores spaces', () => {
+    expect(camelCaseToDashCase('Primary Button')).toBe('primary button')
+  })
+
   it('should revert a dash to camel case transition', () => {
     expect(camelCaseToDashCase(dashCaseToCamelCase('primary-button'))).toBe('primary-button')
   })
diff --git a/packages/teleport-shared/__tests__/utils/uidl-utils.ts b/packages/teleport-shared/__tests__/utils/uidl-utils.ts
index c020eaba..5b96739d 100644
--- a/packages/teleport-shared/__tests__/utils/uidl-utils.ts
+++ b/packages/teleport-shared/__tests__/utils/uidl-utils.ts
@@ -5,12 +5,11 @@ import {
   transformAttributesAssignmentsToJson,
   findFirstElementNode,
   extractRoutes,
-  getComponentPath,
+  getComponentFolderPath,
   getComponentFileName,
   getStyleFileName,
   getTemplateFileName,
   getRepeatIteratorNameAndKey,
-  extractPageOptions,
   prefixAssetsPath,
   traverseNodes,
   traverseElements,
@@ -32,7 +31,6 @@ import {
   UIDLRepeatNode,
   UIDLDynamicReference,
   UIDLSlotNode,
-  UIDLStateDefinition,
   UIDLAttributeValue,
   ComponentUIDL,
 } from '@teleporthq/teleport-types'
@@ -406,11 +404,11 @@ describe('getTemplateFileName', () => {
   })
 })
 
-describe('getComponentPath', () => {
+describe('getComponentFolderPath', () => {
   const testComponent = component('MyComponent', elementNode('random'))
 
   it('returns an empty array if no meta path is provided', () => {
-    expect(getComponentPath(testComponent)).toHaveLength(0)
+    expect(getComponentFolderPath(testComponent)).toHaveLength(0)
   })
 
   it('returns the input meta path', () => {
@@ -418,7 +416,7 @@ describe('getComponentPath', () => {
       folderPath: ['one', 'two'],
     }
 
-    const path = getComponentPath(testComponent)
+    const path = getComponentFolderPath(testComponent)
     expect(path).toContain('one')
     expect(path).toContain('two')
     expect(path.length).toBe(2)
@@ -476,72 +474,6 @@ describe('getRepeatIteratorNameAndKey', () => {
   })
 })
 
-describe('extractPageOptions', () => {
-  const routeDefinitions: UIDLStateDefinition = {
-    type: 'string',
-    defaultValue: 'home',
-    values: [
-      {
-        value: 'home',
-        pageOptions: {
-          navLink: '/',
-        },
-      },
-      {
-        value: 'about',
-        pageOptions: {
-          navLink: '/about-us',
-          componentName: 'AboutUs',
-        },
-      },
-      {
-        value: 'contact-us',
-        pageOptions: {
-          navLink: '/team',
-        },
-      },
-      {
-        value: 'no-meta',
-      },
-    ],
-  }
-
-  it('uses the state for a non-declared page', () => {
-    const result = extractPageOptions(routeDefinitions, 'non-declared')
-    expect(result.navLink).toBe('/non-declared')
-    expect(result.fileName).toBe('non-declared')
-    expect(result.componentName).toBe('NonDeclared')
-  })
-
-  it('uses the state for a page without meta', () => {
-    const result = extractPageOptions(routeDefinitions, 'no-meta')
-    expect(result.navLink).toBe('/no-meta')
-    expect(result.fileName).toBe('no-meta')
-    expect(result.componentName).toBe('NoMeta')
-  })
-
-  it('returns values from the meta with defaults from the state', () => {
-    const result = extractPageOptions(routeDefinitions, 'about')
-    expect(result.navLink).toBe('/about-us') // meta value
-    expect(result.fileName).toBe('about') // state value
-    expect(result.componentName).toBe('AboutUs') // meta value
-  })
-
-  it('converts the fileName to index', () => {
-    const result = extractPageOptions(routeDefinitions, 'home', true)
-    expect(result.navLink).toBe('/')
-    expect(result.fileName).toBe('index')
-    expect(result.componentName).toBe('Home')
-  })
-
-  it('uses the path as the fileName', () => {
-    const result = extractPageOptions(routeDefinitions, 'about', true)
-    expect(result.navLink).toBe('/about-us')
-    expect(result.fileName).toBe('about-us')
-    expect(result.componentName).toBe('AboutUs')
-  })
-})
-
 describe('prefixAssetsPath', () => {
   it('returns the original string if the assets identifier is not found', () => {
     expect(prefixAssetsPath('/static', '/no/identifier')).toBe('/no/identifier')
diff --git a/packages/teleport-uidl-resolver/__tests__/resolver.ts b/packages/teleport-uidl-resolver/__tests__/resolver.ts
index 305df738..0119fde8 100644
--- a/packages/teleport-uidl-resolver/__tests__/resolver.ts
+++ b/packages/teleport-uidl-resolver/__tests__/resolver.ts
@@ -61,7 +61,9 @@ describe('resolveUIDL', () => {
     const resolver = new Resolver()
     resolver.addMapping(mapping)
     const resolvedUIDL = resolver.resolveUIDL(uidl, { mapping: extraMapping })
-    expect(resolvedUIDL.name).toBe('ConditionalComponent')
+    expect(resolvedUIDL.name).toBe('Conditional Component')
+    expect(resolvedUIDL.outputOptions.fileName).toBe('conditional-component')
+    expect(resolvedUIDL.outputOptions.componentClassName).toBe('ConditionalComponent')
     expect(resolvedUIDL.node.type).toBe('element')
     expect(resolvedUIDL.stateDefinitions.isVisible.type).toBe('boolean')
   })
diff --git a/packages/teleport-uidl-resolver/__tests__/utils.ts b/packages/teleport-uidl-resolver/__tests__/utils.ts
index 4474359d..1e1eadd1 100644
--- a/packages/teleport-uidl-resolver/__tests__/utils.ts
+++ b/packages/teleport-uidl-resolver/__tests__/utils.ts
@@ -277,19 +277,13 @@ describe('resolveNavlinks', () => {
     )
   })
 
-  it('does not change the attribute if no route is present', () => {
+  it('throws an error if no route is present', () => {
     const navlink = elementNode('navlink', {
       transitionTo: staticNode('non-existing-state'),
     })
 
-    const warn = jest.spyOn(global.console, 'warn')
-
-    resolveNavlinks(navlink, routeDef)
-
-    expect(warn).toHaveBeenCalledWith(
-      "No navlink was defined for router state: 'non-existing-state'. Falling back to '/non-existing-state'"
-    )
-    expect(navlink.content.attrs.transitionTo.content).toBe('/non-existing-state')
+    expect(() => resolveNavlinks(navlink, routeDef)).toThrow()
+    expect(navlink.content.attrs.transitionTo.content).toBe('non-existing-state')
   })
 })
 
diff --git a/packages/teleport-uidl-validator/__tests__/validator/index.ts b/packages/teleport-uidl-validator/__tests__/validator/index.ts
index a5c91f7a..55e2950c 100644
--- a/packages/teleport-uidl-validator/__tests__/validator/index.ts
+++ b/packages/teleport-uidl-validator/__tests__/validator/index.ts
@@ -149,7 +149,7 @@ Index variable is used but the "useIndex" meta information is false.,
 The component "Navbars" is not defined in the UIDL's component section.,
 The following components have different name than their key: Navbar,OneComponent,
 Root Node contains elements of type "static". It should contain only elements of type "conditional",
-Root Node contains a route that don't have a specified state: /about.`
+Root Node contains a route that don't have a specified state: abouts.`
       )
     })
     it('returns error if route is missing from state definitions', () => {