|
| 1 | +# Migration Guide: @cooklang/cooklang-ts → @cooklang/cooklang |
| 2 | + |
| 3 | +This guide helps you migrate from the TypeScript-native `@cooklang/cooklang-ts` (v1.x) to the new WASM-powered `@cooklang/cooklang` (v0.17+). |
| 4 | + |
| 5 | +## Why Migrate? |
| 6 | + |
| 7 | +The new WASM implementation offers: |
| 8 | + |
| 9 | +- **Better performance**: Native-speed parsing via WebAssembly |
| 10 | +- **Consistent behavior**: Shares implementation with the official Rust parser |
| 11 | +- **Easier maintenance**: Updates to the Cooklang spec are implemented once |
| 12 | +- **Active development**: This is the future of Cooklang parsing for JavaScript/TypeScript |
| 13 | + |
| 14 | +## Quick Migration Checklist |
| 15 | + |
| 16 | +- [ ] Update package name in `package.json` |
| 17 | +- [ ] Update import statements |
| 18 | +- [ ] Change `Recipe` class instantiation to `Parser.parse()` |
| 19 | +- [ ] Update property access (e.g., `cookwares` → `cookware`) |
| 20 | +- [ ] Remove deprecated methods like `toCooklang()` and `getImageURL()` |
| 21 | +- [ ] Test your application thoroughly |
| 22 | + |
| 23 | +## Installation |
| 24 | + |
| 25 | +```bash |
| 26 | +# Uninstall old package |
| 27 | +npm uninstall @cooklang/cooklang-ts |
| 28 | + |
| 29 | +# Install new package |
| 30 | +npm install @cooklang/cooklang |
| 31 | +``` |
| 32 | + |
| 33 | +## API Changes |
| 34 | + |
| 35 | +### Package Name |
| 36 | + |
| 37 | +```typescript |
| 38 | +// Old |
| 39 | +import { Recipe, Parser } from '@cooklang/cooklang-ts'; |
| 40 | + |
| 41 | +// New |
| 42 | +import { Parser } from '@cooklang/cooklang'; |
| 43 | +``` |
| 44 | + |
| 45 | +### Recipe Parsing |
| 46 | + |
| 47 | +The biggest change: the `Recipe` class is removed. Use `Parser.parse()` directly. |
| 48 | + |
| 49 | +```typescript |
| 50 | +// Old |
| 51 | +import { Recipe } from '@cooklang/cooklang-ts'; |
| 52 | +const recipe = new Recipe(source); |
| 53 | + |
| 54 | +// New |
| 55 | +import { Parser } from '@cooklang/cooklang'; |
| 56 | +const parser = new Parser(); |
| 57 | +const recipe = parser.parse(source); |
| 58 | +``` |
| 59 | + |
| 60 | +### Property Names |
| 61 | + |
| 62 | +Some property names have changed: |
| 63 | + |
| 64 | +```typescript |
| 65 | +// Old |
| 66 | +recipe.cookwares // Array of cookware items |
| 67 | +recipe.steps // Array of steps |
| 68 | + |
| 69 | +// New |
| 70 | +recipe.cookware // Array of cookware items (singular) |
| 71 | +recipe.sections // Array of sections (each section contains content/steps) |
| 72 | +``` |
| 73 | + |
| 74 | +### Metadata Access |
| 75 | + |
| 76 | +Metadata access is the same: |
| 77 | + |
| 78 | +```typescript |
| 79 | +// Both old and new |
| 80 | +recipe.metadata.servings |
| 81 | +recipe.metadata.source |
| 82 | +``` |
| 83 | + |
| 84 | +### Ingredients and Cookware |
| 85 | + |
| 86 | +The structure is different: |
| 87 | + |
| 88 | +```typescript |
| 89 | +// Old |
| 90 | +recipe.ingredients // Flat array of all ingredients |
| 91 | +recipe.cookwares // Flat array of all cookware |
| 92 | + |
| 93 | +// New |
| 94 | +recipe.ingredients // Still available |
| 95 | +recipe.cookware // Singular name |
| 96 | +``` |
| 97 | + |
| 98 | +### Steps vs Sections |
| 99 | + |
| 100 | +The new parser uses "sections" instead of "steps": |
| 101 | + |
| 102 | +```typescript |
| 103 | +// Old |
| 104 | +recipe.steps.forEach(step => { |
| 105 | + step.forEach(item => { |
| 106 | + if ('value' in item) { |
| 107 | + console.log(item.value); // Text content |
| 108 | + } else { |
| 109 | + console.log(item.type, item.name); // ingredient/cookware/timer |
| 110 | + } |
| 111 | + }); |
| 112 | +}); |
| 113 | + |
| 114 | +// New |
| 115 | +recipe.sections.forEach(section => { |
| 116 | + section.content.forEach(step => { |
| 117 | + step.items.forEach(item => { |
| 118 | + if (item.type === 'text') { |
| 119 | + console.log(item.value); |
| 120 | + } else if (item.type === 'ingredient') { |
| 121 | + console.log(item.name); |
| 122 | + } |
| 123 | + }); |
| 124 | + }); |
| 125 | +}); |
| 126 | +``` |
| 127 | + |
| 128 | +### Removed Features |
| 129 | + |
| 130 | +The following features from the old package are **not available** in the new WASM version: |
| 131 | + |
| 132 | +#### `toCooklang()` Method |
| 133 | + |
| 134 | +The `Recipe.toCooklang()` method that generated Cooklang source from a recipe object is removed. |
| 135 | + |
| 136 | +```typescript |
| 137 | +// Old (NO LONGER AVAILABLE) |
| 138 | +const recipe = new Recipe(source); |
| 139 | +const cooklangString = recipe.toCooklang(); |
| 140 | + |
| 141 | +// Workaround: Keep the original source if you need it |
| 142 | +const originalSource = source; |
| 143 | +const recipe = parser.parse(source); |
| 144 | +// Use originalSource when needed |
| 145 | +``` |
| 146 | + |
| 147 | +#### `getImageURL()` Function |
| 148 | + |
| 149 | +The helper function for constructing image URLs is removed. |
| 150 | + |
| 151 | +```typescript |
| 152 | +// Old (NO LONGER AVAILABLE) |
| 153 | +import { getImageURL } from '@cooklang/cooklang-ts'; |
| 154 | +const url = getImageURL('Baked Potato', { extension: 'jpg', step: 2 }); |
| 155 | + |
| 156 | +// Workaround: Implement your own helper |
| 157 | +function getImageURL(name: string, options?: { step?: number; extension?: 'png' | 'jpg' }) { |
| 158 | + const ext = options?.extension || 'png'; |
| 159 | + const step = options?.step ? `.${options.step}` : ''; |
| 160 | + return `${name}${step}.${ext}`; |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +#### Shopping List |
| 165 | + |
| 166 | +The shopping list feature is not yet exposed in the WASM bindings: |
| 167 | + |
| 168 | +```typescript |
| 169 | +// Old (NO LONGER AVAILABLE) |
| 170 | +recipe.shoppingList |
| 171 | + |
| 172 | +// Workaround: Build your own shopping list from ingredients |
| 173 | +const shoppingList = recipe.ingredients.reduce((acc, ing) => { |
| 174 | + // Your custom logic here |
| 175 | + return acc; |
| 176 | +}, {}); |
| 177 | +``` |
| 178 | + |
| 179 | +## Value Extraction |
| 180 | + |
| 181 | +The new package provides helper functions for working with quantity values: |
| 182 | + |
| 183 | +```typescript |
| 184 | +import { getNumericValue, extractNumericRange } from '@cooklang/cooklang'; |
| 185 | + |
| 186 | +const ingredient = recipe.ingredients[0]; |
| 187 | +const value = ingredient.quantity?.value; |
| 188 | + |
| 189 | +// Get a single numeric value (for ranges, returns start) |
| 190 | +const numeric = getNumericValue(value); // 2.5 |
| 191 | + |
| 192 | +// Get range values |
| 193 | +const range = extractNumericRange(value); // { start: 2, end: 3 } |
| 194 | +``` |
| 195 | + |
| 196 | +## Complete Example |
| 197 | + |
| 198 | +### Before (v1.x) |
| 199 | + |
| 200 | +```typescript |
| 201 | +import { Recipe, Parser, getImageURL } from '@cooklang/cooklang-ts'; |
| 202 | + |
| 203 | +const source = ` |
| 204 | +>> servings: 4 |
| 205 | +Add @salt and @pepper to taste. |
| 206 | +`; |
| 207 | + |
| 208 | +const recipe = new Recipe(source); |
| 209 | + |
| 210 | +console.log(recipe.metadata.servings); // "4" |
| 211 | +console.log(recipe.ingredients[0].name); // "salt" |
| 212 | + |
| 213 | +// Convert back to Cooklang |
| 214 | +const cooklangString = recipe.toCooklang(); |
| 215 | + |
| 216 | +// Get image URL |
| 217 | +const imageUrl = getImageURL('My Recipe', { step: 1 }); |
| 218 | +``` |
| 219 | + |
| 220 | +### After (v0.17+) |
| 221 | + |
| 222 | +```typescript |
| 223 | +import { Parser } from '@cooklang/cooklang'; |
| 224 | + |
| 225 | +const source = ` |
| 226 | +>> servings: 4 |
| 227 | +Add @salt and @pepper to taste. |
| 228 | +`; |
| 229 | + |
| 230 | +const parser = new Parser(); |
| 231 | +const recipe = parser.parse(source); |
| 232 | + |
| 233 | +console.log(recipe.metadata.servings); // "4" |
| 234 | +console.log(recipe.ingredients[0].name); // "salt" |
| 235 | + |
| 236 | +// toCooklang() not available - keep original source if needed |
| 237 | +const originalSource = source; |
| 238 | + |
| 239 | +// getImageURL() not available - use custom helper |
| 240 | +function getImageURL(name: string, options?: { step?: number; extension?: 'png' | 'jpg' }) { |
| 241 | + const ext = options?.extension || 'png'; |
| 242 | + const step = options?.step ? `.${options.step}` : ''; |
| 243 | + return `${name}${step}.${ext}`; |
| 244 | +} |
| 245 | + |
| 246 | +const imageUrl = getImageURL('My Recipe', { step: 1 }); |
| 247 | +``` |
| 248 | + |
| 249 | +## Type Definitions |
| 250 | + |
| 251 | +The new package includes full TypeScript type definitions. Import types as needed: |
| 252 | + |
| 253 | +```typescript |
| 254 | +import type { |
| 255 | + Recipe, |
| 256 | + Ingredient, |
| 257 | + Cookware, |
| 258 | + Timer, |
| 259 | + Section, |
| 260 | + Content, |
| 261 | + Step, |
| 262 | + Item, |
| 263 | + Value, |
| 264 | + Quantity |
| 265 | +} from '@cooklang/cooklang'; |
| 266 | +``` |
| 267 | + |
| 268 | +## Testing Your Migration |
| 269 | + |
| 270 | +After migrating, ensure you: |
| 271 | + |
| 272 | +1. **Run your test suite** - All tests should pass with the new API |
| 273 | +2. **Check recipe parsing** - Verify recipes parse correctly |
| 274 | +3. **Validate data access** - Ensure you can access all needed data from parsed recipes |
| 275 | +4. **Test edge cases** - Complex recipes, special characters, etc. |
| 276 | + |
| 277 | +## Getting Help |
| 278 | + |
| 279 | +If you encounter issues during migration: |
| 280 | + |
| 281 | +- Check the [README](./README.md) for API reference |
| 282 | +- Open an issue on [GitHub](https://github.com/cooklang/cooklang-rs/issues) |
| 283 | +- Review the [Cooklang specification](https://cooklang.org/docs/spec/) |
| 284 | + |
| 285 | +## Version Numbering |
| 286 | + |
| 287 | +Don't be alarmed by the 0.x version number! This package: |
| 288 | + |
| 289 | +- Tracks the Rust core version (currently 0.17.x) |
| 290 | +- Is production-ready and actively maintained |
| 291 | +- Will bump to 1.0 when the Rust core does |
| 292 | + |
| 293 | +The version number reflects synchronization with the Rust parser, not stability. |
0 commit comments