|
1 | 1 | # typeshot |
2 | 2 |
|
| 3 | +`typeshot` is a code generator like snapshot library, for TypeScript Declaration Files. |
| 4 | + |
| 5 | +## Motivation |
| 6 | + |
| 7 | + |
| 8 | +TypeScript developers have |
| 9 | + |
3 | 10 | ## Installation |
4 | 11 |
|
| 12 | +```sh |
| 13 | +$ npm i -D typeshot |
| 14 | +$ npm i -D typescript prettier ts-node # You can skip them if already installed. |
| 15 | +``` |
| 16 | + |
5 | 17 | ## Usage |
6 | 18 |
|
| 19 | +### Example |
| 20 | + |
| 21 | +#### `sample.typeshot.ts` |
| 22 | + |
| 23 | +Register types that you want to take snapshot. |
| 24 | + |
| 25 | +```ts |
| 26 | +import typeshot from 'typeshot'; |
| 27 | + |
| 28 | +typeshot.configuration({ output: './sample.generated.ts' })` |
| 29 | +// DO NOT EDIT - GENERATED FILE |
| 30 | +`; |
| 31 | +// typeshot-output-header |
| 32 | + |
| 33 | +// eslint-disable-next-line no-console |
| 34 | +console.log('Loaded Generated File!'); |
| 35 | + |
| 36 | +// typeshot-header |
| 37 | +import type { Type, GenericType } from './another-file'; |
| 38 | +type EntryMap<T extends object> = { |
| 39 | + [K in keyof T]: readonly [K, T[K]]; |
| 40 | +}; |
| 41 | + |
| 42 | +// typeshot-main |
| 43 | +typeshot.takeStatic<typeshot.Expand<Type>>('UniqueKey-0', 'TypeName')` |
| 44 | +// Descriptions of '${typeshot.TemplateSymbols.NAME}' |
| 45 | +export ${typeshot.TemplateSymbols.DECLARATION} |
| 46 | +export type ${typeshot.TemplateSymbols.NAME}Array = ${typeshot.TemplateSymbols.NAME}[]; |
| 47 | +export const ${typeshot.TemplateSymbols.NAME}__sample: ${typeshot.TemplateSymbols.NAME} = { /* ... */ } as any; |
| 48 | +`; |
| 49 | + |
| 50 | +typeshot.takeStatic<EntryMap<Type>>('UniqueKey-1', 'TypeNameForEntryMap')` |
| 51 | +export ${typeshot.TemplateSymbols.DECLARATION} |
| 52 | +`; |
| 53 | + |
| 54 | +interface DynamicTypeshotProps { |
| 55 | + param: string; |
| 56 | +} |
| 57 | +const takeDynamic = typeshot |
| 58 | + .createDynamic<GenericType<typeshot.T>>('UniqueKey-2') |
| 59 | + .parameters<DynamicTypeshotProps>(({ param }) => [param]) |
| 60 | + .names(({ param }) => `GenericType__${param.toUpperCase()}`)` |
| 61 | +// ${({ param }) => param} |
| 62 | +export ${typeshot.TemplateSymbols.DECLARATION} |
| 63 | +`; |
| 64 | + |
| 65 | +takeDynamic({ param: 'foo' }); |
| 66 | +takeDynamic({ param: 'bar' }); |
| 67 | +``` |
| 68 | + |
| 69 | +#### `another-file.ts` |
| 70 | + |
| 71 | +This is dependency file that is loaded from `sample.typeshot.ts`. |
| 72 | + |
| 73 | +```ts |
| 74 | +export interface Type { |
| 75 | + foo: string; |
| 76 | + bar: { |
| 77 | + baz: number; |
| 78 | + qux: Date; |
| 79 | + }; |
| 80 | +} |
| 81 | + |
| 82 | +export type GenericType<T extends string> = { |
| 83 | + [K in T]: { type: K }; |
| 84 | +}; |
| 85 | +``` |
| 86 | + |
| 87 | +#### run-typeshot.ts |
| 88 | +There is no CLI for `typeshot` yet. |
| 89 | +```ts |
| 90 | +import runTypeshot from 'typeshot/program'; |
| 91 | + |
| 92 | +runTypeshot({ test: /\.typeshot\.ts$/ }); |
| 93 | +``` |
| 94 | + |
| 95 | +Execute via `ts-node` |
| 96 | + |
| 97 | +```sh |
| 98 | +$ ts-node --files run-typeshot.ts |
| 99 | +``` |
| 100 | + |
| 101 | +#### `sample.generated.ts` |
| 102 | + |
| 103 | +This is the output file of `typeshot` with `sample.typeshot.ts`. |
| 104 | + |
| 105 | +```ts |
| 106 | +// DO NOT EDIT - GENERATED FILE |
| 107 | +// typeshot-output-header |
| 108 | +// eslint-disable-next-line no-console |
| 109 | +console.log('Loaded Generated File!'); |
| 110 | +// Descriptions of 'TypeName' |
| 111 | +export type TypeName = { |
| 112 | + foo: string; |
| 113 | + bar: { |
| 114 | + baz: number; |
| 115 | + qux: Date; |
| 116 | + }; |
| 117 | +}; |
| 118 | +export type TypeNameArray = TypeName[]; |
| 119 | +export const TypeName__sample: TypeName = { |
| 120 | + /* ... */ |
| 121 | +} as any; |
| 122 | + |
| 123 | +export type TypeNameForEntryMap = { |
| 124 | + foo: readonly ['foo', string]; |
| 125 | + bar: readonly [ |
| 126 | + 'bar', |
| 127 | + { |
| 128 | + baz: number; |
| 129 | + qux: Date; |
| 130 | + }, |
| 131 | + ]; |
| 132 | +}; |
| 133 | + |
| 134 | +// foo |
| 135 | +export type GenericType__FOO = { |
| 136 | + foo: { |
| 137 | + type: 'foo'; |
| 138 | + }; |
| 139 | +}; |
| 140 | + |
| 141 | +// bar |
| 142 | +export type GenericType__BAR = { |
| 143 | + bar: { |
| 144 | + type: 'bar'; |
| 145 | + }; |
| 146 | +}; |
| 147 | + |
| 148 | +export {}; |
| 149 | +``` |
| 150 | + |
| 151 | +### API |
| 152 | + |
| 153 | +#### `typeshot.takeStatic` |
| 154 | + |
| 155 | +This is the most basic usage of `typeshot`. You can take a snapshot of type. |
| 156 | + |
| 157 | +```ts |
| 158 | +typeshot.takeStatic<SnapshotEntryType>('Unique Key', 'TypeName')` |
| 159 | +// output code as string |
| 160 | +${typeshot.TemplateSymbols.NAME} |
| 161 | +${typeshot.TemplateSymbols.CONTENT} |
| 162 | +${typeshot.TemplateSymbols.DECLARATION} |
| 163 | +`; |
| 164 | +``` |
| 165 | +The type parameter at where `SnapshotEntryType` is placed, is the entry. `typeshot` serialize actual type structure of the entry. |
| 166 | + |
| 167 | +The first argument is a key as pure string literal, don't use template string. Each key should be unique in the file. |
| 168 | + |
| 169 | +The second argument is name of generated type. |
| 170 | + |
| 171 | +The tail of statement, tagged template string is written in output after symbols are replaced with generated type. It is described later section about symbols. |
| 172 | + |
| 173 | +#### `typeshot.createDynamic` |
| 174 | +```ts |
| 175 | +interface DynamicTypeshotProps { |
| 176 | + param: string; |
| 177 | +} |
| 178 | +const takeDynamic = typeshot |
| 179 | + .createDynamic<GenericType<typeshot.T>>('UniqueKey') |
| 180 | + .parameters<DynamicTypeshotProps>(({ param }) => [param]) |
| 181 | + .names(({ param }) => param.toUpperCase())` |
| 182 | +// ${({ param }) => param} |
| 183 | +export ${typeshot.TemplateSymbols.DECLARATION} |
| 184 | +`; |
| 185 | + |
| 186 | +takeDynamic({ param: 'foo' }); |
| 187 | +takeDynamic({ param: 'bar' }); |
| 188 | +``` |
| 189 | + |
| 190 | +The difference from `typeshot.takeStatic` is that the second argument doesn't exist, two extra phases `parameters` and `names` are added, `typeshot.T` is available in entry type, and function is available in tagged template. |
| 191 | + |
| 192 | +The `parameters` phase is for specifying replacement of `typeshot.T`.<br> |
| 193 | +You can use `typeshot.T` multiple times. The order that aliases are replaced is left to right, even if the entry type is deeply nested. |
| 194 | + |
| 195 | +The `name` phase is for specifying name of generated type.<br> |
| 196 | +You can use two kinds of name descriptors. One is to use string as same as the second argument of `takeStatic`.<br> |
| 197 | +Another one is object(`Record<string, string>`) or array(`string[]`). When you use this way, the generated type is not entry type itself. `typeshot` use each type of property that is referred with keys of the object or array, and value of the key will be used as name. |
| 198 | + |
| 199 | +As the example, you can use function in the tagged template. |
| 200 | + |
| 201 | +You can set the type of argument of each function in the type parameter of `parameters` phase. |
| 202 | + |
| 203 | +#### `typeshot.configuration` |
| 204 | + |
| 205 | +You can specify path of output file and header content of output file. |
| 206 | + |
| 207 | +```ts |
| 208 | +typeshot.configuration({ output: './sample.generated.ts' })` |
| 209 | +// DO NOT EDIT - GENERATED FILE |
| 210 | +`; |
| 211 | +``` |
| 212 | + |
| 213 | +#### Symbols |
| 214 | +| group | name | description | |
| 215 | +|:--------------- |:----------- |:----------- | |
| 216 | +|`TemplateSymbols`|`NAME` | name of output type | |
| 217 | +| |`CONTENT` | content of output type, generally object literal | |
| 218 | +| |`DECLARATION`| completed content of output type, as type alias | |
| 219 | + |
| 220 | +#### Types |
| 221 | +| name | description | |
| 222 | +|:--------------- |:----------- | |
| 223 | +|`typeshot.T` | Type alias that will be replaced by dynamic parameter. See `typeshot.createDynamic` section about detail. | |
| 224 | +|`typeshot.Expand`| Generic type that helps to serialize types declared as `interface`.<br> For example, if `Type` in the example above was not wrapped with `typeshot.Expand`, it is serialized to `Type`, not object literal. | |
| 225 | + |
| 226 | +### Section Comments |
| 227 | + |
| 228 | +`typeshot` splits input file into several sections by spesific comments. Supports only single line comment. |
| 229 | +```ts |
| 230 | +console.log('unknown, will be ignored'); |
| 231 | + |
| 232 | +// section-comment-A |
| 233 | +console.log('section A start'); |
| 234 | +/* statements */ |
| 235 | +console.log('section A end'); |
| 236 | + |
| 237 | +// section-comment-B |
| 238 | +console.log('section B start'); |
| 239 | +/* statements */ |
| 240 | +console.log('section B end'); |
| 241 | +``` |
| 242 | + |
| 243 | +Statements in each section are treated in different way. |
| 244 | + |
| 245 | +| section name | kept or not in output | what kind of code should be written | |
| 246 | +|:---------------------- |:--------------------- |:----------------------------------- | |
| 247 | +|before sections | removed | used by also generated code (**transformed** `main`) | |
| 248 | +|`typeshot-output-header`| kept on the top | used by also generated code (**transformed** `main`) | |
| 249 | +|`typeshot-header` | removed | used by only **raw** `main` section | |
| 250 | +|`typeshot-main` | removed | `typeshot`'s code | |
| 251 | +|`typeshot-footer` | removed | used by only **raw** `main` section | |
| 252 | +|`typeshot-output-footer`| kept on the bottom | used by also generated code (**transformed** `main`) | |
| 253 | + |
| 254 | + |
| 255 | +### Execution |
| 256 | + |
| 257 | +There is no CLI for `typeshot` yet. |
| 258 | + |
| 259 | +Typeshot find entry files via tsconfig. So you have to include typeshot files by your tsconfig.<br> |
| 260 | +If you don't want to include them in your production tsconfig, you can create another tsconfig file for typeshot. |
| 261 | +```ts |
| 262 | +import runTypeshot from 'typeshot/program'; |
| 263 | + |
| 264 | +runTypeshot({ test: /\.typeshot\.ts$/ }); |
| 265 | +``` |
| 266 | + |
| 267 | +```sh |
| 268 | +$ ts-node --files run-typeshot.ts |
| 269 | +``` |
| 270 | + |
| 271 | +#### Options |
| 272 | + |
| 273 | +| name | type | description | |
| 274 | +|:--------------- |:------- | :---------- | |
| 275 | +|`test` | RegExp | **required**, pattern of typeshot file | |
| 276 | +|`project` | string | optional, path to `tsconfig.json` | |
| 277 | +|`prettierOptions`| object | optional, options of `prettier` | |
| 278 | +|`basePath` | string | optional, base path to find `tsconfig.json` and `.prettierrc` if they are ommited | |
| 279 | + |
| 280 | +## TODO |
| 281 | +- implement CLI |
| 282 | + |
7 | 283 | ## Committers |
8 | 284 |
|
9 | 285 | * Shota Hatada ([@whatasoda](https://github.com/whatasoda)) |
|
0 commit comments