diff --git a/.github/contributing.md b/.github/contributing.md index 25628e2ba..afdae6711 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -17,7 +17,11 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before ## Pull Request Guidelines -- Checkout a topic branch from a base branch, e.g. `main`, and merge back against that branch. +- Vue core has two primary work branches: `main` and `minor`. + + - If your pull request is a feature that adds new API surface, it should be submitted against the `minor` branch. + + - Otherwise, it should be submitted against the `main` branch. - [Make sure to tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). This allows us to directly make minor edits / refactors and saves a lot of time. diff --git a/.github/git-branch-workflow.excalidraw b/.github/git-branch-workflow.excalidraw new file mode 100644 index 000000000..dd9127938 --- /dev/null +++ b/.github/git-branch-workflow.excalidraw @@ -0,0 +1,1746 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "arrow", + "version": 799, + "versionNonce": 529220601, + "isDeleted": false, + "id": "Gao2krnDddLMCj468JSWD", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 860.0129225738813, + "y": 663.9911710635109, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 133.75296854079784, + "height": 149.58016791936518, + "seed": 1415631543, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "hDC6an14QljktaZCUhcPF", + "focus": 0.09950793234484598, + "gap": 1.2432497743127229 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 25.209039386719837, + 85.96948921803892 + ], + [ + 133.75296854079784, + 149.58016791936518 + ] + ] + }, + { + "type": "arrow", + "version": 563, + "versionNonce": 290881303, + "isDeleted": false, + "id": "N3wyyEU7TQ8BsOQgxCmlR", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 292.88008929085873, + "y": 660.7027503334302, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 936.9972134376155, + "height": 1.3184243543457796, + "seed": 534235417, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 936.9972134376155, + -1.3184243543457796 + ] + ] + }, + { + "type": "arrow", + "version": 302, + "versionNonce": 883286489, + "isDeleted": false, + "id": "nRDWQs5nQa37yzCWTBiXC", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 293.1231624544633, + "y": 820.6017661012943, + "strokeColor": "#f08c00", + "backgroundColor": "#b2f2bb", + "width": 790.7091601354882, + "height": 0.35284814071621895, + "seed": 515907671, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "ggogfJT7E_bbfEog7Hjnp", + "focus": -0.14000162237652433, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 790.7091601354882, + -0.35284814071621895 + ] + ] + }, + { + "type": "text", + "version": 36, + "versionNonce": 981763127, + "isDeleted": false, + "id": "ZPdMAnEUq5Jgj1W07Zqiw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 292.0450153578305, + "y": 619.3959946602608, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 46.875, + "height": 24, + "seed": 1311694519, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 3, + "text": "main", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "main", + "lineHeight": 1.2, + "baseline": 20 + }, + { + "type": "text", + "version": 94, + "versionNonce": 18759353, + "isDeleted": false, + "id": "g9IkEIfu4vA8Qkwtw01Hi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 290.88990199912035, + "y": 779.1760596323645, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 58.59375, + "height": 24, + "seed": 329886135, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 3, + "text": "minor", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "minor", + "lineHeight": 1.2, + "baseline": 20 + }, + { + "type": "ellipse", + "version": 50, + "versionNonce": 1442112855, + "isDeleted": false, + "id": "RrdEQ7hwgGGDPhzDnuZj1", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 361.55609907891005, + "y": 649.8742329483416, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 2077639991, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 79, + "versionNonce": 1547173785, + "isDeleted": false, + "id": "Zmp49FKWxGSzKnVKomjQc", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 427.3015090315691, + "y": 650.256485100784, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 372652121, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 76, + "versionNonce": 586949239, + "isDeleted": false, + "id": "UOl9nLBksM7RPdH9mzjJa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 490.9435520120701, + "y": 651.2601420343765, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 508667545, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 120, + "versionNonce": 874947705, + "isDeleted": false, + "id": "oMC55V0VO_hOXoZ1se8Kl", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 555.4481126698772, + "y": 650.7975189165487, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1914963513, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 66, + "versionNonce": 39762839, + "isDeleted": false, + "id": "DZY5DC5uVP7-U5c3ngIZ4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 622.5167031502219, + "y": 649.3743647489936, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 165914713, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 107, + "versionNonce": 1689103705, + "isDeleted": false, + "id": "Vsw6oIiTM3fQypkiCic3f", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 690.330195260967, + "y": 650.6681412649529, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 280044345, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "lwYvAs-7FTjcwxKjcx0KV", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 148, + "versionNonce": 1986194201, + "isDeleted": false, + "id": "D14w9erv_2l53mINe2nSt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 361.004283792179, + "y": 810.2809579853473, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1203257975, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 179, + "versionNonce": 1172811511, + "isDeleted": false, + "id": "6WO8xOpG0rf673b_bT0m7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 426.74969374483805, + "y": 810.6632101377896, + "strokeColor": "#f08c00", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 2056706967, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "mE8Mu0qKfFaWPCC5vmF_f", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 173, + "versionNonce": 820518905, + "isDeleted": false, + "id": "VB9U8oH-78hf530hIb_mG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 490.391736725339, + "y": 811.6668670713822, + "strokeColor": "#f08c00", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1149587639, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 218, + "versionNonce": 1227143191, + "isDeleted": false, + "id": "Bxv1hcS0VmxUwI0JLFH97", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 554.8962973831461, + "y": 811.2042439535543, + "strokeColor": "#f08c00", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1864901079, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "M14Q0Uo1DBy2Ss2SOFSgW", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 167, + "versionNonce": 1387509977, + "isDeleted": false, + "id": "4v23gkfhy-hzk18YdkfLz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 621.9648878634908, + "y": 809.7810897859994, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 462671607, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "vEF1cIIYYWKm84KLKqEz3", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 200, + "versionNonce": 774085943, + "isDeleted": false, + "id": "AtEf7o4WZQn4Zxq8EN5fH", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 689.7783799742359, + "y": 811.0748663019584, + "strokeColor": "#f08c00", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1414322199, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "3heKY3vfe3-6ni4dX7Uqo", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 199, + "versionNonce": 1834563001, + "isDeleted": false, + "id": "ugDby5sBv4NKdNt8eC1sg", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 762.6179978227377, + "y": 810.2986003923828, + "strokeColor": "#f08c00", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1598537015, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 211, + "versionNonce": 407428695, + "isDeleted": false, + "id": "Fwe4F2sB_0jptOZGYsusj", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 837.1081608628116, + "y": 810.859236882632, + "strokeColor": "#f08c00", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1340669527, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "M14Q0Uo1DBy2Ss2SOFSgW", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 57, + "versionNonce": 335287961, + "isDeleted": false, + "id": "mE8Mu0qKfFaWPCC5vmF_f", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 437.60867586595543, + "y": 830.4227236701945, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 0.5232394659406623, + "height": 33.25787987764363, + "seed": 482155929, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "startBinding": { + "elementId": "6WO8xOpG0rf673b_bT0m7", + "focus": -0.1727591064041787, + "gap": 1.046152088903881 + }, + "endBinding": { + "elementId": "JALHBtowuh3_a86loej2x", + "focus": 0.015156451076917701, + "gap": 15.586906139714472 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.5232394659406623, + 33.25787987764363 + ] + ] + }, + { + "type": "arrow", + "version": 59, + "versionNonce": 1248394103, + "isDeleted": false, + "id": "AI-_jSAuzesxTqwRvpk0s", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 501.2878833373983, + "y": 652.3088851192829, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffc9c9", + "width": 0, + "height": 40.40111211199792, + "seed": 1052632343, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -40.40111211199792 + ] + ] + }, + { + "type": "arrow", + "version": 261, + "versionNonce": 693099385, + "isDeleted": false, + "id": "lwYvAs-7FTjcwxKjcx0KV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 786.7392304423553, + "y": 649.6016935672433, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffc9c9", + "width": 0, + "height": 40.40111211199792, + "seed": 1233043511, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "startBinding": { + "elementId": "s0PKxsWTJSDbQeEl_WI-C", + "focus": 0.016372633695398757, + "gap": 1 + }, + "endBinding": { + "elementId": "9ia1Uwc5X0fRw5iaahmcT", + "focus": 0.025318405829282714, + "gap": 14.862364635333904 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0, + -40.40111211199792 + ] + ] + }, + { + "type": "text", + "version": 121, + "versionNonce": 952661143, + "isDeleted": false, + "id": "qWW8uxDIcV3Bkj28uvRLr", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 454.32425448306674, + "y": 537.8854189061962, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 93.75, + "height": 57.599999999999994, + "seed": 809847769, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "patch\nrelease\ne.g. 3.3.8", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "patch\nrelease\ne.g. 3.3.8", + "lineHeight": 1.2, + "baseline": 53 + }, + { + "type": "text", + "version": 257, + "versionNonce": 1838679129, + "isDeleted": false, + "id": "9ia1Uwc5X0fRw5iaahmcT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 741.0510307156029, + "y": 536.7382168199114, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 93.75, + "height": 57.599999999999994, + "seed": 213765431, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "lwYvAs-7FTjcwxKjcx0KV", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "patch\nrelease\ne.g. 3.3.9", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "patch\nrelease\ne.g. 3.3.9", + "lineHeight": 1.2, + "baseline": 53 + }, + { + "type": "text", + "version": 222, + "versionNonce": 1528547767, + "isDeleted": false, + "id": "JALHBtowuh3_a86loej2x", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 350.7264132088442, + "y": 879.2675096875524, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 168.75, + "height": 57.599999999999994, + "seed": 41180921, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "mE8Mu0qKfFaWPCC5vmF_f", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "pre minor\nrelease\ne.g. 3.4.0-alpha.1", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "pre minor\nrelease\ne.g. 3.4.0-alpha.1", + "lineHeight": 1.2, + "baseline": 53 + }, + { + "type": "arrow", + "version": 345, + "versionNonce": 1286082873, + "isDeleted": false, + "id": "3heKY3vfe3-6ni4dX7Uqo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 699.5281288163526, + "y": 831.0290882554708, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 0.5502191262773977, + "height": 33.25154356841597, + "seed": 627698359, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613071, + "link": null, + "locked": false, + "startBinding": { + "elementId": "AtEf7o4WZQn4Zxq8EN5fH", + "focus": -0.05612657009295625, + "gap": 1.1451322685712295 + }, + "endBinding": { + "elementId": "9t6qH-tAxVUexkHHi2pd2", + "focus": 0.015156451076917755, + "gap": 15.586906139714358 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.5502191262773977, + 33.25154356841597 + ] + ] + }, + { + "type": "text", + "version": 365, + "versionNonce": 1049066199, + "isDeleted": false, + "id": "9t6qH-tAxVUexkHHi2pd2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 617.3409291322284, + "y": 879.8675379636011, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 159.375, + "height": 57.599999999999994, + "seed": 1013545943, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "3heKY3vfe3-6ni4dX7Uqo", + "type": "arrow" + } + ], + "updated": 1698927613071, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "pre minor\nrelease\ne.g. 3.4.0-beta.1", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "pre minor\nrelease\ne.g. 3.4.0-beta.1", + "lineHeight": 1.2, + "baseline": 53 + }, + { + "type": "arrow", + "version": 788, + "versionNonce": 1810072089, + "isDeleted": false, + "id": "vEF1cIIYYWKm84KLKqEz3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 630.3597332113623, + "y": 667.2735668205443, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 2.258228100583324, + "height": 140.75112333166828, + "seed": 2091697367, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "4v23gkfhy-hzk18YdkfLz", + "focus": 0.13930391883256707, + "gap": 1.8256906627890626 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.8426514015177418, + 69.09942755691065 + ], + [ + 2.258228100583324, + 140.75112333166828 + ] + ] + }, + { + "type": "arrow", + "version": 687, + "versionNonce": 2017318649, + "isDeleted": false, + "id": "M14Q0Uo1DBy2Ss2SOFSgW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 370.5976915356099, + "y": 667.5155013947814, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 1.5329291446666957, + "height": 145.39303664953377, + "seed": 361678233, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.34892760581925586, + 83.56228079137543 + ], + [ + 1.1840015388474399, + 145.39303664953377 + ] + ] + }, + { + "type": "text", + "version": 537, + "versionNonce": 342487319, + "isDeleted": false, + "id": "CHAOOJMz7tNaG1VsG_uzT", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 384.81046417498214, + "y": 725.4677076298137, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 131.25, + "height": 57.599999999999994, + "seed": 1656007289, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "merge main\ninto minor\nbefore release", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "merge main\ninto minor\nbefore release", + "lineHeight": 1.2, + "baseline": 53 + }, + { + "type": "ellipse", + "version": 202, + "versionNonce": 876253145, + "isDeleted": false, + "id": "hDC6an14QljktaZCUhcPF", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 993.0386151813434, + "y": 810.335845473903, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1433430105, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "Gao2krnDddLMCj468JSWD", + "type": "arrow" + } + ], + "updated": 1698927613072, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 1525, + "versionNonce": 777631287, + "isDeleted": false, + "id": "ces8IwHCpQlTnELpjFDIn", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1092.5386800881793, + "y": 827.5114796878765, + "strokeColor": "#f08c00", + "backgroundColor": "#ffc9c9", + "width": 0.3315362017829102, + "height": 49.45191086419197, + "seed": 225867737, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "8rWUxp-jRNGrGRmhHHfm4", + "focus": -0.2047594653982401, + "gap": 10.392197401393389 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.3315362017829102, + 49.45191086419197 + ] + ] + }, + { + "type": "text", + "version": 894, + "versionNonce": 1173171385, + "isDeleted": false, + "id": "8rWUxp-jRNGrGRmhHHfm4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1047.251646167428, + "y": 887.3555879534618, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 112.5, + "height": 57.599999999999994, + "seed": 1600918713, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "ces8IwHCpQlTnELpjFDIn", + "type": "arrow" + } + ], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "stable minor\nrelease\ne.g. 3.4.0", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "stable minor\nrelease\ne.g. 3.4.0", + "lineHeight": 1.2, + "baseline": 53 + }, + { + "type": "ellipse", + "version": 201, + "versionNonce": 78435447, + "isDeleted": false, + "id": "3RHuRn_evSK0YUe02B4MY", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 909.9742423218671, + "y": 810.4142561718397, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 1199705047, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 371, + "versionNonce": 2093872087, + "isDeleted": false, + "id": "9h2Cu__8owLUgUGjGcWDe", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 848.4414471158692, + "y": 650.826922928275, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 603147257, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 361, + "versionNonce": 1981618457, + "isDeleted": false, + "id": "s0PKxsWTJSDbQeEl_WI-C", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 777.1778842958995, + "y": 650.2466837635417, + "strokeColor": "#2f9e44", + "backgroundColor": "#b2f2bb", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 326722777, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "lwYvAs-7FTjcwxKjcx0KV", + "type": "arrow" + } + ], + "updated": 1698927613072, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 871, + "versionNonce": 1528156247, + "isDeleted": false, + "id": "3JAdSa7kqqSDSom5ZFDoE", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 904.3603861670398, + "y": 707.2413714353705, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 140.625, + "height": 57.599999999999994, + "seed": 1011049431, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "final merge\nmain into minor\nbefore release", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "final merge\nmain into minor\nbefore release", + "lineHeight": 1.2, + "baseline": 53 + }, + { + "type": "arrow", + "version": 591, + "versionNonce": 1714373785, + "isDeleted": false, + "id": "7kFBLq2Iczmj0lVnVk8Ad", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1100.7141458557703, + "y": 814.2034531496416, + "strokeColor": "#2f9e44", + "backgroundColor": "#ffffff", + "width": 127.38209933342364, + "height": 144.5383600420214, + "seed": 25829591, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "Y7VXnuc9QEz2N2l9i0xrc", + "focus": 0.3932764551319699, + "gap": 5.928572790502042 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 88.94909573964219, + -43.721805169626464 + ], + [ + 127.38209933342364, + -144.5383600420214 + ] + ] + }, + { + "type": "text", + "version": 1208, + "versionNonce": 1254600055, + "isDeleted": false, + "id": "gwFWlPLabuYhxCOweJjWz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1223.0464288187204, + "y": 725.1565933898091, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 150, + "height": 38.4, + "seed": 51102743, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "main merge minor\n(fast forward)", + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "main merge minor\n(fast forward)", + "lineHeight": 1.2, + "baseline": 34 + }, + { + "type": "ellipse", + "version": 597, + "versionNonce": 1760381305, + "isDeleted": false, + "id": "Y7VXnuc9QEz2N2l9i0xrc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1227.4473966637659, + "y": 647.6689320688656, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 412038615, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "7kFBLq2Iczmj0lVnVk8Ad", + "type": "arrow" + } + ], + "updated": 1698927613072, + "link": null, + "locked": false + }, + { + "type": "ellipse", + "version": 547, + "versionNonce": 1585505943, + "isDeleted": false, + "id": "ggogfJT7E_bbfEog7Hjnp", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1083.7911569735343, + "y": 809.5203742153592, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 18.814646969963974, + "height": 18.814646969963974, + "seed": 741463161, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "id": "nRDWQs5nQa37yzCWTBiXC", + "type": "arrow" + } + ], + "updated": 1698927613072, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 229, + "versionNonce": 1935127129, + "isDeleted": false, + "id": "eU-EgpwDD42CLYUEIDLaD", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 305.8405004265049, + "y": 389.31989430571576, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 581.25, + "height": 19.2, + "seed": 1086231577, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "- merge feature PRs into, and release minors from minor branch", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- merge feature PRs into, and release minors from minor branch", + "lineHeight": 1.2, + "baseline": 15 + }, + { + "type": "text", + "version": 397, + "versionNonce": 116088535, + "isDeleted": false, + "id": "Kt6VBAVD4sLM4IexsRGoX", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 305.4136207977353, + "y": 358.61173442109686, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 618.75, + "height": 19.2, + "seed": 273353945, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927617946, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "- merge fix / chore PRs into, and release patches from main branch", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- merge fix / chore PRs into, and release patches from main branch", + "lineHeight": 1.2, + "baseline": 15 + }, + { + "type": "text", + "version": 459, + "versionNonce": 440532793, + "isDeleted": false, + "id": "JwKEdnU6H_Nu74WbEAX5M", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 305.6723761009271, + "y": 418.3724478537203, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 459.375, + "height": 19.2, + "seed": 1001222329, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "- merge main into minor before each minor release", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- merge main into minor before each minor release", + "lineHeight": 1.2, + "baseline": 15 + }, + { + "type": "text", + "version": 602, + "versionNonce": 1108720119, + "isDeleted": false, + "id": "mb9ZoP803MiH7MTO8wH-2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 305.0895924262568, + "y": 447.44321411383333, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "width": 534.375, + "height": 19.2, + "seed": 264651479, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "- fast forward main to minor after a stable minor release", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- fast forward main to minor after a stable minor release", + "lineHeight": 1.2, + "baseline": 15 + }, + { + "type": "text", + "version": 612, + "versionNonce": 1588872441, + "isDeleted": false, + "id": "IfJPOFiwrCibpaBQqc5g-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 646.7131179044119, + "y": 724.4984335940012, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "width": 131.25, + "height": 57.599999999999994, + "seed": 1301100087, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1698927613072, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 3, + "text": "merge main\ninto minor\nbefore release", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "merge main\ninto minor\nbefore release", + "lineHeight": 1.2, + "baseline": 53 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/.github/git-branch-workflow.png b/.github/git-branch-workflow.png new file mode 100644 index 000000000..6c8ee07d4 Binary files /dev/null and b/.github/git-branch-workflow.png differ diff --git a/.github/issue-workflow.png b/.github/issue-workflow.png new file mode 100644 index 000000000..92b1de063 Binary files /dev/null and b/.github/issue-workflow.png differ diff --git a/.github/maintenance.md b/.github/maintenance.md new file mode 100644 index 000000000..8d4317c6b --- /dev/null +++ b/.github/maintenance.md @@ -0,0 +1,122 @@ +# Vue Core Maintenance Handbook + +Unlike [contributing.md](./contributing.md), which targets external contributors, this document is mainly intended for team members responsible for maintaining the project. It provides guidelines on how to triage issues, review & merge PRs, and publish releases. However, it should also be valuable to external contributors even if you are not a maintainer, as it gives you a better idea of how the maintainers operate, and how you can better collaborate with them. And who knows - maybe one day you will join as a maintainer as well! + +- [Issue Triage Workflow](#issue-triage-workflow) +- [Pull Request Review Guidelines](#pull-request-review-guidelines) + - [Reviewing a Fix](#reviewing-a-fix) + - [Reviewing a Refactor](#reviewing-a-refactor) + - [Reviewing a Feature](#reviewing-a-feature) + - [Common Considerations for All PRs](#common-considerations-for-all-prs) +- [PR Merge Rules for Team Members](#pr-merge-rules-for-team-members) +- [Git Branch and Release Workflow](#git-branch-and-release-workflow) + +## Issue Triage Workflow + +![Workflow](./issue-workflow.png) + +## Pull Request Review Guidelines + +The first step of reviewing a PR is to identify its purpose. We can usually put a PR in one of these categories: + +- **Fix**: fixes some wrong behavior. Usually associated with an issue that has a reproduction of the behavior being fixed. +- **Refactor**: improves performance or code quality, but does not affect behavior. +- **Feature**: implements something that increases the public API surface. + +Depending on the type of the PR, different considerations need to be taken into account. + +### Reviewing a Fix + +- Is the PR fixing a well defined issue / bug report? + - If not, ask to clarify context / provide reproduction or failing test case +- In most cases, a fix PR should include a test case that fails without the fix. +- Is it the right fix? + - If not, guide user to rework the PR. + - If the needed change is small and obvious, can directly push to the PR or add inline suggestions to reduce the back-and-forth. +- Is the cost justified? + - Sometimes the fix for a rare edge case might be introducing disproportionately large overhead (perf or code size). We should try our best to reduce the overhead to make the fix a reasonable tradeoff. +- If the reviewer is not sure about a fix, try to leave a comment explaining the concerns / reservations so the contributor at least gets some feedback. + +#### Verifying a Fix + +- **Always locally verify that the fix indeed fixes the original behavior, either through a reproduction or a failing test case.** +- We will run [ecosystem-ci](https://github.com/vuejs/ecosystem-ci) before every release, but if you are concerned about the potential impact of a change, it never hurts to manually run ecosystem-ci by leaving a `/ecosystem-ci run` comment (only works for team members). +- Take extra caution with snapshot tests! The CI can be "passing" even if the code generated in the snapshot contains bugs. It's best to always accompany a snapshot test with extra `expect(code).toMatch(...)` assertions. + +### Reviewing a Refactor + +- Performance: if a refactor PR claims to improve performance, there should be benchmarks showcasing said performance unless the improvement is self-explanatory. + +- Code quality / stylistic PRs: we should be conservative on merging this type PRs because (1) they can be subjective in many cases, and (2) they often come with large git diffs, causing merge conflicts with other pending PRs, and leading to unwanted noise when tracing changes through git history. Use your best judgement on this type of PRs on whether they are worth it. + + - For PRs in this category that are approved, do not merge immediately. Group them before releasing a new minor, after all feature-oriented PRs are merged. + +### Reviewing a Feature + +- Feature PRs should always have clear context and explanation on why the feature should be added, ideally in the form of an RFC. If the PR doesn't explain what real-world problem it is solving, ask the contributor to clarify. + +- Decide if the feature should require an RFC process. The line isn't always clear, but a rough criteria is whether it is augmenting an existing API vs. adding a new API. Some examples: + + - Adding a new built-in component or directive is "significant" and definitely requires an RFC. + - Template syntax additions like adding a new `v-on` modifier or a new `v-bind` syntax sugar are "substantial". It would be nice to have an RFC for it, but a detailed explanation on the use case and reasoning behind the design directly in the PR itself can be acceptable. + - Small, low-impact additions like exposing a new utility type or adding a new app config option can be self-explanatory, but should still provide enough context in the PR. + +- Always ask if the use case can be solved with existing APIs. Vue already has a pretty large API surface, so we want to make sure every new addition either solves something that wasn't possible before, or significantly improves the DX of a common task. + +### Common Considerations for All PRs + +- Scope: a PR should only contain changes directly related to the problem being addressed. It should not contain unnecessary code style changes. + +- Implementation: code style should be consistent with the rest of the codebase, follow common best practices. Prefer code that is boring but easy to understand over "clever" code. + +- Size: bundle size matters. We have a GitHub action that compares the size change for every PR. We should always aim to realize the desired changes with the smallest amount of code size increase. + + - Sometimes we need to compare the size increase vs. perceived benefits to decide whether a change is justifiable. Also take extra care to make sure added code can be tree-shaken if not needed. + + - Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable. + + - Runtime code is more sensitive to size increase than compiler code. + + - Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`. + +- Performance + - Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code. + +- Potential Breakage + - avoiding runtime behavior breakage is the highest priority + - if not sure, use `ecosystem-ci` to verify! + - some fix inevitably cause behavior change, these must be discussed case-by-case + - type level breakage (e.g upgrading TS) is possible between minors + +## PR Merge Rules for Team Members + +Given that the PR meets the review requirements: + +- Chore / dependencies bumps: can merge directly. +- Fixes / refactors: can merge with two or more approvals from team members. + - If you believe a PR looks good but you are not 100% confident to merge, label with "ready for merge" and Evan will provide a final review before merging. +- Features: if approved by two or more team members, label with "ready to merge". Evan will review periodically, or they can be raised and discussed at team meetings. + +## Git Branch and Release Workflow + +We use two primary work branches: `main` and `minor`. + +- The `main` branch is for stable releases. Changes that are bug fixes or refactors that do not affect the public API surface should land in this branch. We periodically release patch releases from the `main` branch. + +- The `minor` branch is the WIP branch for the next minor release. Changes that are new features or those that affect public API behavior should land in this branch. We will periodically release pre-releases (alpha / beta) for the next minor from this branch. + +Before each release, we merge latest `main` into `minor` so it would include the latest bug fixes. + +When the minor is ready, we do a final merge of `main` into `minor`, and then release a stable minor from this branch (e.g. `3.4.0`). After that, the `main` branch is fast-forwarded to the release commit, so the two branches are synced at each stable minor release. + +![Workflow](./git-branch-workflow.png) + +### Reasoning Behind the Workflow + +The reason behind this workflow is to allow merging and releasing of fixes and features in parallel. In the past, we used a linear trunk-based development model. While the linear model results in a clean git history, the downside is that we need to be careful about when to merge patches vs. features. + +Vue typically groups a number of features with the same scope in a minor release. We don't want to release a minor just because we happened to merge a feature PR along with a bunch of small bug fixes. So we usually "wait" until we feel we are ready to start working on a minor release before merging feature PRs. + +But in reality, there are always bugs to fix and patch release to work on - this caused the intervals between minors to drag on longer than we had hoped, and many feature PRs were left waiting for a long period of time. + +This is why we decided to separate bug fixes and feature PRs into separate branches. With this two-branch model, we are able to merge and release both types of changes in parallel. diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 1c81ece39..5e7bb63c2 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -17,7 +17,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Set node version to 18 - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18 cache: pnpm @@ -30,4 +30,4 @@ jobs: - name: Run prettier run: pnpm run format - - uses: autofix-ci/action@d3e591514b99d0fca6779455ff8338516663f7cc + - uses: autofix-ci/action@bee19d72e71787c12ca0f29de72f2833e437e4c9 diff --git a/.github/workflows/canary-minor.yml b/.github/workflows/canary-minor.yml index 2aa6db12b..27fbd42c9 100644 --- a/.github/workflows/canary-minor.yml +++ b/.github/workflows/canary-minor.yml @@ -20,7 +20,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Set node version to 18 - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18 registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 61265c2d0..61490232f 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -18,7 +18,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.node-version' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4ec93e3e..493ab2950 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.node-version' cache: 'pnpm' @@ -45,7 +45,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.node-version' cache: 'pnpm' @@ -74,7 +74,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.node-version' cache: 'pnpm' @@ -97,7 +97,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.node-version' cache: 'pnpm' diff --git a/.github/workflows/ecosystem-ci-trigger.yml b/.github/workflows/ecosystem-ci-trigger.yml index bd3a27494..25adf7c85 100644 --- a/.github/workflows/ecosystem-ci-trigger.yml +++ b/.github/workflows/ecosystem-ci-trigger.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run') steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: script: | const user = context.payload.sender.login @@ -43,7 +43,7 @@ jobs: }) throw new Error('not allowed') } - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 id: get-pr-data with: script: | @@ -58,7 +58,7 @@ jobs: branchName: pr.head.ref, repo: pr.head.repo.full_name } - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 id: trigger env: COMMENT: ${{ github.event.comment.body }} diff --git a/.github/workflows/lock-closed-issues.yml b/.github/workflows/lock-closed-issues.yml index cf01a5f6c..68a7d6c7a 100644 --- a/.github/workflows/lock-closed-issues.yml +++ b/.github/workflows/lock-closed-issues.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'vuejs/core' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v4 + - uses: dessant/lock-threads@v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} issue-inactive-days: '14' diff --git a/.github/workflows/size-data.yml b/.github/workflows/size-data.yml index 83141e242..bb82aa18d 100644 --- a/.github/workflows/size-data.yml +++ b/.github/workflows/size-data.yml @@ -25,7 +25,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.node-version' cache: pnpm diff --git a/.github/workflows/size-report.yml b/.github/workflows/size-report.yml index cdfce9a97..78ae44bb7 100644 --- a/.github/workflows/size-report.yml +++ b/.github/workflows/size-report.yml @@ -27,7 +27,7 @@ jobs: uses: pnpm/action-setup@v2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: '.node-version' cache: pnpm diff --git a/CHANGELOG.md b/CHANGELOG.md index 858b722d1..5863fc31a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [3.3.8](https://github.com/vuejs/core/compare/v3.3.7...v3.3.8) (2023-11-06) + + +### Bug Fixes + +* **compile-sfc:** support `Error` type in `defineProps` ([#5955](https://github.com/vuejs/core/issues/5955)) ([a989345](https://github.com/vuejs/core/commit/a9893458ec519aae442e1b99e64e6d74685cd22c)) +* **compiler-core:** known global should be shadowed by local variables in expression rewrite ([#9492](https://github.com/vuejs/core/issues/9492)) ([a75d1c5](https://github.com/vuejs/core/commit/a75d1c5c6242e91a73cc5ba01e6da620dea0b3d9)), closes [#9482](https://github.com/vuejs/core/issues/9482) +* **compiler-sfc:** fix dynamic directive arguments usage check for slots ([#9495](https://github.com/vuejs/core/issues/9495)) ([b39fa1f](https://github.com/vuejs/core/commit/b39fa1f8157647859331ce439c42ae016a49b415)), closes [#9493](https://github.com/vuejs/core/issues/9493) +* **deps:** update dependency @vue/repl to ^2.6.2 ([#9536](https://github.com/vuejs/core/issues/9536)) ([5cef325](https://github.com/vuejs/core/commit/5cef325f41e3b38657c72fa1a38dedeee1c7a60a)) +* **deps:** update dependency @vue/repl to ^2.6.3 ([#9540](https://github.com/vuejs/core/issues/9540)) ([176d590](https://github.com/vuejs/core/commit/176d59058c9aecffe9da4d4311e98496684f06d4)) +* **hydration:** fix tagName access eeror on comment/text node hydration mismatch ([dd8a0cf](https://github.com/vuejs/core/commit/dd8a0cf5dcde13d2cbd899262a0e07f16e14e489)), closes [#9531](https://github.com/vuejs/core/issues/9531) +* **types:** avoid exposing lru-cache types in generated dts ([462aeb3](https://github.com/vuejs/core/commit/462aeb3b600765e219ded2ee9a0ed1e74df61de0)), closes [#9521](https://github.com/vuejs/core/issues/9521) +* **warn:** avoid warning on empty children with Suspense ([#3962](https://github.com/vuejs/core/issues/3962)) ([405f345](https://github.com/vuejs/core/commit/405f34587a63a5f1e3d147b9848219ea98acc22d)) + + + # [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28) diff --git a/README.md b/README.md index 17d9abc6b..cbc05311a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Vue.js is an MIT-licensed open source project with its ongoing development made

- sponsors + sponsors

diff --git a/package.json b/package.json index 1655ff8b5..7f0a46bc1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "version": "3.4.0-alpha.1", - "packageManager": "pnpm@8.9.2", + "packageManager": "pnpm@8.10.5", "type": "module", "scripts": { "dev": "node scripts/dev.js", @@ -27,9 +27,9 @@ "dev-esm": "node scripts/dev.js -if esm-bundler-runtime", "dev-compiler": "run-p \"dev template-explorer\" serve", "dev-sfc": "run-s dev-sfc-prepare dev-sfc-run", - "dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-compiler-cjs", + "dev-sfc-prepare": "node scripts/pre-dev-sfc.js || npm run build-all-cjs", "dev-sfc-serve": "vite packages/sfc-playground --host", - "dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve", + "dev-sfc-run": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev vue -ipf esm-browser-runtime\" \"dev server-renderer -if esm-bundler\" dev-sfc-serve", "serve": "serve", "open": "open http://localhost:3000/packages/template-explorer/local.html", "build-sfc-playground": "run-s build-all-cjs build-runtime-esm build-ssr-esm build-sfc-playground-self", @@ -57,40 +57,40 @@ "node": ">=18.12.0" }, "devDependencies": { - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "@rollup/plugin-alias": "^5.0.1", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.0.1", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^5.0.4", "@rollup/plugin-terser": "^0.4.4", - "@types/hash-sum": "^1.0.1", - "@types/node": "^20.8.7", - "@typescript-eslint/parser": "^6.8.0", + "@types/hash-sum": "^1.0.2", + "@types/node": "^20.9.2", + "@typescript-eslint/parser": "^6.11.0", "@vitest/coverage-istanbul": "^0.34.6", "@vue/consolidate": "0.17.3", "conventional-changelog-cli": "^4.1.0", "enquirer": "^2.4.1", "esbuild": "^0.19.5", "esbuild-plugin-polyfill-node": "^0.3.0", - "eslint": "^8.52.0", - "eslint-plugin-jest": "^27.4.3", + "eslint": "^8.54.0", + "eslint-plugin-jest": "^27.6.0", "estree-walker": "^2.0.2", "execa": "^8.0.1", "jsdom": "^22.1.0", - "lint-staged": "^15.0.2", + "lint-staged": "^15.1.0", "lodash": "^4.17.21", "magic-string": "^0.30.5", "markdown-table": "^3.0.3", - "marked": "^9.1.2", + "marked": "^9.1.6", "minimist": "^1.2.8", "npm-run-all": "^4.1.5", "picocolors": "^1.0.0", - "prettier": "^3.0.3", + "prettier": "^3.1.0", "pretty-bytes": "^6.1.1", "pug": "^3.0.2", - "puppeteer": "~21.4.0", + "puppeteer": "~21.5.1", "rimraf": "^5.0.5", "rollup": "^4.1.4", "rollup-plugin-dts": "^6.1.0", @@ -102,9 +102,9 @@ "terser": "^5.22.0", "todomvc-app-css": "^2.4.3", "tslib": "^2.6.2", - "tsx": "^3.14.0", + "tsx": "^4.1.4", "typescript": "^5.2.2", - "vite": "^4.5.0", + "vite": "^5.0.0", "vitest": "^0.34.6" } } diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap index c72e02298..434ebcbcf 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/transformExpressions.spec.ts.snap @@ -2,7 +2,7 @@ exports[`compiler: expression transform > bindingMetadata > inline mode 1`] = ` "(_ctx, _cache) => { - return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.props) + \\" \\" + _toDisplayString(_unref(setup)) + \\" \\" + _toDisplayString(setupConst) + \\" \\" + _toDisplayString(_ctx.data) + \\" \\" + _toDisplayString(_ctx.options), 1 /* TEXT */)) + return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.props) + \\" \\" + _toDisplayString(_unref(setup)) + \\" \\" + _toDisplayString(setupConst) + \\" \\" + _toDisplayString(_ctx.data) + \\" \\" + _toDisplayString(_ctx.options) + \\" \\" + _toDisplayString(isNaN.value), 1 /* TEXT */)) }" `; @@ -10,6 +10,48 @@ exports[`compiler: expression transform > bindingMetadata > non-inline mode 1`] "const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue return function render(_ctx, _cache, $props, $setup, $data, $options) { - return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString($props.props) + \\" \\" + _toDisplayString($setup.setup) + \\" \\" + _toDisplayString($data.data) + \\" \\" + _toDisplayString($options.options), 1 /* TEXT */)) + return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString($props.props) + \\" \\" + _toDisplayString($setup.setup) + \\" \\" + _toDisplayString($data.data) + \\" \\" + _toDisplayString($options.options) + \\" \\" + _toDisplayString($setup.isNaN), 1 /* TEXT */)) +}" +`; + +exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for loop 1`] = ` +"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +return function render(_ctx, _cache, $props, $setup, $data, $options) { + return (_openBlock(), _createElementBlock(\\"div\\", { + onClick: () => { + for (let i = 0; i < _ctx.list.length; i++) { + _ctx.log(i) + } + } + }, null, 8 /* PROPS */, [\\"onClick\\"])) +}" +`; + +exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...in 1`] = ` +"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +return function render(_ctx, _cache, $props, $setup, $data, $options) { + return (_openBlock(), _createElementBlock(\\"div\\", { + onClick: () => { + for (const x in _ctx.list) { + _ctx.log(x) + } + } + }, null, 8 /* PROPS */, [\\"onClick\\"])) +}" +`; + +exports[`compiler: expression transform > bindingMetadata > should not prefix temp variable of for...of 1`] = ` +"const { openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue + +return function render(_ctx, _cache, $props, $setup, $data, $options) { + return (_openBlock(), _createElementBlock(\\"div\\", { + onClick: () => { + for (const x of _ctx.list) { + _ctx.log(x) + } + } + }, null, 8 /* PROPS */, [\\"onClick\\"])) }" `; diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap index e59df2d54..983fe1223 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap @@ -85,7 +85,7 @@ return function render(_ctx, _cache) { return (_openBlock(), _createElementBlock(\\"input\\", { \\"foo-value\\": model, \\"onUpdate:fooValue\\": $event => ((model) = $event) - }, null, 40 /* PROPS, HYDRATE_EVENTS */, [\\"foo-value\\", \\"onUpdate:fooValue\\"])) + }, null, 40 /* PROPS, NEED_HYDRATION */, [\\"foo-value\\", \\"onUpdate:fooValue\\"])) } }" `; diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index a1ae013a8..97559369d 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -1089,7 +1089,7 @@ describe('compiler: element transform', () => { }) }) - test('HYDRATE_EVENTS', () => { + test('NEED_HYDRATION for v-on', () => { // ignore click events (has dedicated fast path) const { node } = parseWithElementTransform(`
`, { directiveTransforms: { @@ -1108,12 +1108,24 @@ describe('compiler: element transform', () => { } ) expect(node2.patchFlag).toBe( - genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS]) + genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]) + ) + }) + + test('NEED_HYDRATION for v-bind.prop', () => { + const { node } = parseWithBind(`
`) + expect(node.patchFlag).toBe( + genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]) + ) + + const { node: node2 } = parseWithBind(`
`) + expect(node2.patchFlag).toBe( + genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]) ) }) // #5870 - test('HYDRATE_EVENTS on dynamic component', () => { + test('NEED_HYDRATION on dynamic component', () => { const { node } = parseWithElementTransform( ``, { @@ -1123,7 +1135,7 @@ describe('compiler: element transform', () => { } ) expect(node.patchFlag).toBe( - genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS]) + genFlagText([PatchFlags.PROPS, PatchFlags.NEED_HYDRATION]) ) }) }) diff --git a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts index 686794c23..162291136 100644 --- a/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts @@ -506,7 +506,8 @@ describe('compiler: expression transform', () => { data: BindingTypes.DATA, options: BindingTypes.OPTIONS, reactive: BindingTypes.SETUP_REACTIVE_CONST, - literal: BindingTypes.LITERAL_CONST + literal: BindingTypes.LITERAL_CONST, + isNaN: BindingTypes.SETUP_REF } function compileWithBindingMetadata( @@ -522,19 +523,56 @@ describe('compiler: expression transform', () => { test('non-inline mode', () => { const { code } = compileWithBindingMetadata( - `
{{ props }} {{ setup }} {{ data }} {{ options }}
` + `
{{ props }} {{ setup }} {{ data }} {{ options }} {{ isNaN }}
` ) expect(code).toMatch(`$props.props`) expect(code).toMatch(`$setup.setup`) + expect(code).toMatch(`$setup.isNaN`) expect(code).toMatch(`$data.data`) expect(code).toMatch(`$options.options`) expect(code).toMatch(`_ctx, _cache, $props, $setup, $data, $options`) expect(code).toMatchSnapshot() }) + test('should not prefix temp variable of for...in', () => { + const { code } = compileWithBindingMetadata( + `
` + ) + expect(code).not.toMatch(`_ctx.x`) + expect(code).toMatchSnapshot() + }) + + test('should not prefix temp variable of for...of', () => { + const { code } = compileWithBindingMetadata( + `
` + ) + expect(code).not.toMatch(`_ctx.x`) + expect(code).toMatchSnapshot() + }) + + test('should not prefix temp variable of for loop', () => { + const { code } = compileWithBindingMetadata( + `
` + ) + expect(code).not.toMatch(`_ctx.i`) + expect(code).toMatchSnapshot() + }) + test('inline mode', () => { const { code } = compileWithBindingMetadata( - `
{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }}
`, + `
{{ props }} {{ setup }} {{ setupConst }} {{ data }} {{ options }} {{ isNaN }}
`, { inline: true } ) expect(code).toMatch(`__props.props`) @@ -542,6 +580,7 @@ describe('compiler: expression transform', () => { expect(code).toMatch(`_toDisplayString(setupConst)`) expect(code).toMatch(`_ctx.data`) expect(code).toMatch(`_ctx.options`) + expect(code).toMatch(`isNaN.value`) expect(code).toMatchSnapshot() }) diff --git a/packages/compiler-core/__tests__/transforms/vFor.spec.ts b/packages/compiler-core/__tests__/transforms/vFor.spec.ts index 1c7646e5e..60a9378eb 100644 --- a/packages/compiler-core/__tests__/transforms/vFor.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vFor.spec.ts @@ -674,8 +674,8 @@ describe('compiler: v-for', () => { patchFlag: !disableTracking ? genFlagText(PatchFlags.STABLE_FRAGMENT) : keyed - ? genFlagText(PatchFlags.KEYED_FRAGMENT) - : genFlagText(PatchFlags.UNKEYED_FRAGMENT), + ? genFlagText(PatchFlags.KEYED_FRAGMENT) + : genFlagText(PatchFlags.UNKEYED_FRAGMENT), children: { type: NodeTypes.JS_CALL_EXPRESSION, callee: RENDER_LIST, diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index e32771ab1..95c5f00ad 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -32,12 +32,12 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme", "dependencies": { - "@babel/parser": "^7.23.0", - "@vue/shared": "3.4.0-alpha.1", + "@babel/parser": "^7.23.3", + "@vue/shared": "workspace:*", "estree-walker": "^2.0.2", "source-map-js": "^1.0.2" }, "devDependencies": { - "@babel/types": "^7.23.0" + "@babel/types": "^7.23.3" } } diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index b52ce2899..1f1e3896a 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -165,6 +165,19 @@ export function walkBlockDeclarations( ) { if (stmt.declare || !stmt.id) continue onIdent(stmt.id) + } else if ( + stmt.type === 'ForOfStatement' || + stmt.type === 'ForInStatement' || + stmt.type === 'ForStatement' + ) { + const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left + if (variable && variable.type === 'VariableDeclaration') { + for (const decl of variable.declarations) { + for (const id of extractIdentifiers(decl.id)) { + onIdent(id) + } + } + } } } } diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 2b88ab0cf..ceae49982 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -448,8 +448,8 @@ function genAssets( __COMPAT__ && type === 'filter' ? RESOLVE_FILTER : type === 'component' - ? RESOLVE_COMPONENT - : RESOLVE_DIRECTIVE + ? RESOLVE_COMPONENT + : RESOLVE_DIRECTIVE ) for (let i = 0; i < assets.length; i++) { let id = assets[i] diff --git a/packages/compiler-core/src/compile.ts b/packages/compiler-core/src/compile.ts index 95e371896..01cb560cc 100644 --- a/packages/compiler-core/src/compile.ts +++ b/packages/compiler-core/src/compile.ts @@ -41,8 +41,8 @@ export function getBaseTransformPreset( transformExpression ] : __BROWSER__ && __DEV__ - ? [transformExpression] - : []), + ? [transformExpression] + : []), transformSlotOutlet, transformElement, trackSlotScopes, diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index b72ad028b..c237239db 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -811,8 +811,8 @@ function parseAttribute( (isPropShorthand || startsWith(name, ':') ? 'bind' : startsWith(name, '@') - ? 'on' - : 'slot') + ? 'on' + : 'slot') let arg: ExpressionNode | undefined if (match[2]) { @@ -1063,7 +1063,7 @@ function parseTextData( ) { return rawText } else { - // DATA or RCDATA containing "&"". Entity decoding required. + // DATA or RCDATA containing "&". Entity decoding is required. return context.options.decodeEntities( rawText, mode === TextModes.ATTRIBUTE_VALUE diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 04f85679c..2053e26ce 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -238,8 +238,8 @@ export function createTransformContext( const removalIndex = node ? list.indexOf(node) : context.currentNode - ? context.childIndex - : -1 + ? context.childIndex + : -1 /* istanbul ignore if */ if (__DEV__ && removalIndex < 0) { throw new Error(`node being removed is not a child of current parent`) diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index 253b6be5e..fd61f0110 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -550,7 +550,7 @@ export function buildProps( ) } else { // directives - const { name, arg, exp, loc } = prop + const { name, arg, exp, loc, modifiers } = prop const isVBind = name === 'bind' const isVOn = name === 'on' @@ -678,6 +678,11 @@ export function buildProps( continue } + // force hydration for v-bind with .prop modifier + if (isVBind && modifiers.includes('prop')) { + patchFlag |= PatchFlags.NEED_HYDRATION + } + const directiveTransform = context.directiveTransforms[name] if (directiveTransform) { // has built-in directive transform. @@ -743,12 +748,12 @@ export function buildProps( patchFlag |= PatchFlags.PROPS } if (hasHydrationEventBinding) { - patchFlag |= PatchFlags.HYDRATE_EVENTS + patchFlag |= PatchFlags.NEED_HYDRATION } } if ( !shouldUseBlock && - (patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) && + (patchFlag === 0 || patchFlag === PatchFlags.NEED_HYDRATION) && (hasRef || hasVnodeHook || runtimeDirectives.length > 0) ) { patchFlag |= PatchFlags.NEED_PATCH diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index eab6b237f..52d1fb42a 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -227,10 +227,15 @@ export function processExpression( const isScopeVarReference = context.identifiers[rawExp] const isAllowedGlobal = isGloballyAllowed(rawExp) const isLiteral = isLiteralWhitelisted(rawExp) - if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) { + if ( + !asParams && + !isScopeVarReference && + !isLiteral && + (!isAllowedGlobal || bindingMetadata[rawExp]) + ) { // const bindings exposed from setup can be skipped for patching but // cannot be hoisted to module scope - if (isConst(bindingMetadata[node.content])) { + if (isConst(bindingMetadata[rawExp])) { node.constType = ConstantTypes.CAN_SKIP_PATCH } node.content = rewriteIdentifier(rawExp) diff --git a/packages/compiler-core/src/transforms/vFor.ts b/packages/compiler-core/src/transforms/vFor.ts index a44532c0d..3f725b71b 100644 --- a/packages/compiler-core/src/transforms/vFor.ts +++ b/packages/compiler-core/src/transforms/vFor.ts @@ -37,7 +37,8 @@ import { isTemplateNode, isSlotOutlet, injectProp, - findDir + findDir, + forAliasRE } from '../utils' import { RENDER_LIST, @@ -94,8 +95,8 @@ export const transformFor = createStructuralDirectiveTransform( const fragmentFlag = isStableFragment ? PatchFlags.STABLE_FRAGMENT : keyProp - ? PatchFlags.KEYED_FRAGMENT - : PatchFlags.UNKEYED_FRAGMENT + ? PatchFlags.KEYED_FRAGMENT + : PatchFlags.UNKEYED_FRAGMENT forNode.codegenNode = createVNodeCall( context, @@ -140,10 +141,10 @@ export const transformFor = createStructuralDirectiveTransform( const slotOutlet = isSlotOutlet(node) ? node : isTemplate && - node.children.length === 1 && - isSlotOutlet(node.children[0]) - ? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this - : null + node.children.length === 1 && + isSlotOutlet(node.children[0]) + ? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this + : null if (slotOutlet) { // or @@ -308,7 +309,6 @@ export function processFor( } } -const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ // This regex doesn't cover the case if key or index aliases have destructuring, // but those do not make sense in the first place, so this works in practice. const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index ffa90ea11..156a8461a 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -349,8 +349,8 @@ export function buildSlots( const slotFlag = hasDynamicSlots ? SlotFlags.DYNAMIC : hasForwardedSlots(node.children) - ? SlotFlags.FORWARDED - : SlotFlags.STABLE + ? SlotFlags.FORWARDED + : SlotFlags.STABLE let slots = createObjectExpression( slotsProperties.concat( diff --git a/packages/compiler-core/src/utils.ts b/packages/compiler-core/src/utils.ts index bd2882b09..baffe5086 100644 --- a/packages/compiler-core/src/utils.ts +++ b/packages/compiler-core/src/utils.ts @@ -519,3 +519,5 @@ export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) { return node } } + +export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ diff --git a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts index 85efeafb8..90eb25c72 100644 --- a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts @@ -20,10 +20,7 @@ describe('stringify static html', () => { } function repeat(code: string, n: number): string { - return new Array(n) - .fill(0) - .map(() => code) - .join('') + return code.repeat(n) } test('should bail on non-eligible static trees', () => { diff --git a/packages/compiler-dom/__tests__/transforms/vModel.spec.ts b/packages/compiler-dom/__tests__/transforms/vModel.spec.ts index dce8f09b0..a67ca5d69 100644 --- a/packages/compiler-dom/__tests__/transforms/vModel.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/vModel.spec.ts @@ -137,6 +137,27 @@ describe('compiler: transform v-model', () => { }) ) }) + + test('should error on dynamic value binding alongside v-model', () => { + const onError = vi.fn() + transformWithModel(``, { + onError + }) + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + code: DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE + }) + ) + }) + + // #3596 + test('should NOT error on static value binding alongside v-model', () => { + const onError = vi.fn() + transformWithModel(``, { + onError + }) + expect(onError).not.toHaveBeenCalled() + }) }) describe('modifiers', () => { diff --git a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts index efc7fee37..79ffcdef0 100644 --- a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts @@ -272,7 +272,7 @@ describe('compiler-dom: transform v-on', () => { // should not treat cached handler as dynamicProp, so it should have no // dynamicProps flags and only the hydration flag expect((root as any).children[0].codegenNode.patchFlag).toBe( - genFlagText(PatchFlags.HYDRATE_EVENTS) + genFlagText(PatchFlags.NEED_HYDRATION) ) expect(prop).toMatchObject({ key: { diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index f39057c07..57df1d53a 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme", "dependencies": { - "@vue/shared": "3.4.0-alpha.1", - "@vue/compiler-core": "3.4.0-alpha.1" + "@vue/shared": "workspace:*", + "@vue/compiler-core": "workspace:*" } } diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts index 0b47cb435..67042b2d7 100644 --- a/packages/compiler-dom/src/transforms/stringifyStatic.ts +++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts @@ -153,8 +153,8 @@ const isStringifiableAttr = (name: string, ns: DOMNamespaces) => { (ns === DOMNamespaces.HTML ? isKnownHtmlAttr(name) : ns === DOMNamespaces.SVG - ? isKnownSvgAttr(name) - : false) || dataAriaRE.test(name) + ? isKnownSvgAttr(name) + : false) || dataAriaRE.test(name) ) } diff --git a/packages/compiler-dom/src/transforms/vModel.ts b/packages/compiler-dom/src/transforms/vModel.ts index 5dff390d3..bc1f6fcd2 100644 --- a/packages/compiler-dom/src/transforms/vModel.ts +++ b/packages/compiler-dom/src/transforms/vModel.ts @@ -4,7 +4,9 @@ import { ElementTypes, findProp, NodeTypes, - hasDynamicKeyVBind + hasDynamicKeyVBind, + findDir, + isStaticArgOf } from '@vue/compiler-core' import { createDOMCompilerError, DOMErrorCodes } from '../errors' import { @@ -32,8 +34,8 @@ export const transformModel: DirectiveTransform = (dir, node, context) => { } function checkDuplicatedValue() { - const value = findProp(node, 'value') - if (value) { + const value = findDir(node, 'bind') + if (value && isStaticArgOf(value.arg, 'value')) { context.onError( createDOMCompilerError( DOMErrorCodes.X_V_MODEL_UNNECESSARY_VALUE, diff --git a/packages/compiler-dom/src/transforms/vOn.ts b/packages/compiler-dom/src/transforms/vOn.ts index 660003b48..e84bbd917 100644 --- a/packages/compiler-dom/src/transforms/vOn.ts +++ b/packages/compiler-dom/src/transforms/vOn.ts @@ -96,14 +96,14 @@ const transformClick = (key: ExpressionNode, event: string) => { return isStaticClick ? createSimpleExpression(event, true) : key.type !== NodeTypes.SIMPLE_EXPRESSION - ? createCompoundExpression([ - `(`, - key, - `) === "onClick" ? "${event}" : (`, - key, - `)` - ]) - : key + ? createCompoundExpression([ + `(`, + key, + `) === "onClick" ? "${event}" : (`, + key, + `)` + ]) + : key } export const transformOn: DirectiveTransform = (dir, node, context) => { diff --git a/packages/compiler-sfc/README.md b/packages/compiler-sfc/README.md index c76618e4a..4f8ff3ac8 100644 --- a/packages/compiler-sfc/README.md +++ b/packages/compiler-sfc/README.md @@ -4,7 +4,7 @@ **Note: as of 3.2.13+, this package is included as a dependency of the main `vue` package and can be accessed as `vue/compiler-sfc`. This means you no longer need to explicitly install this package and ensure its version match that of `vue`'s. Just use the main `vue/compiler-sfc` deep import instead.** -This package contains lower level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue Single File Components (SFCs) into JavaScript. It is used in [vue-loader](https://github.com/vuejs/vue-loader), [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue) and [vite](https://github.com/vitejs/vite). +This package contains lower level utilities that you can use if you are writing a plugin / transform for a bundler or module system that compiles Vue Single File Components (SFCs) into JavaScript. It is used in [vue-loader](https://github.com/vuejs/vue-loader) and [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue). ## API @@ -77,4 +77,4 @@ export default script Options needed for these APIs can be passed via the query string. -For detailed API references and options, check out the source type definitions. For actual usage of these APIs, check out [rollup-plugin-vue](https://github.com/vuejs/rollup-plugin-vue/tree/next) or [vue-loader](https://github.com/vuejs/vue-loader/tree/next). +For detailed API references and options, check out the source type definitions. For actual usage of these APIs, check out [@vitejs/plugin-vue](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) or [vue-loader](https://github.com/vuejs/vue-loader/tree/next). diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index 949c9946d..073874363 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -696,14 +696,14 @@ return { get vMyDir() { return vMyDir } } exports[`SFC compile `) expect(content).toMatch( `return { get FooBar() { return FooBar }, get foo() { return foo }, ` + - `get bar() { return bar } }` + `get bar() { return bar }, get baz() { return baz } }` ) assertCode(content) }) diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap index 1e851cbf4..78a9834d2 100644 --- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap @@ -81,6 +81,24 @@ return { emit } })" `; +exports[`defineEmits > w/ type (interface w/ extends) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +interface Base { (e: 'foo'): void } + interface Emits extends Base { (e: 'bar'): void } + +export default /*#__PURE__*/_defineComponent({ + emits: [\\"bar\\", \\"foo\\"], + setup(__props, { expose: __expose, emit: __emit }) { + __expose(); + + const emit = __emit + +return { emit } +} + +})" +`; + exports[`defineEmits > w/ type (interface) 1`] = ` "import { defineComponent as _defineComponent } from 'vue' interface Emits { (e: 'foo' | 'bar'): void } diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap index 30e00e518..158b5c8f5 100644 --- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap @@ -46,6 +46,51 @@ export default /*#__PURE__*/_defineComponent({ const { foo } = __props +return { } +} + +})" +`; + +exports[`defineProps > should escape names w/ special symbols 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' + +export default /*#__PURE__*/_defineComponent({ + props: { + \\"spa ce\\": { type: null, required: true }, + \\"exclamation!mark\\": { type: null, required: true }, + \\"double\\\\\\"quote\\": { type: null, required: true }, + \\"hash#tag\\": { type: null, required: true }, + \\"dollar$sign\\": { type: null, required: true }, + \\"percentage%sign\\": { type: null, required: true }, + \\"amper&sand\\": { type: null, required: true }, + \\"single'quote\\": { type: null, required: true }, + \\"round(brack)ets\\": { type: null, required: true }, + \\"aste*risk\\": { type: null, required: true }, + \\"pl+us\\": { type: null, required: true }, + \\"com,ma\\": { type: null, required: true }, + \\"do.t\\": { type: null, required: true }, + \\"sla/sh\\": { type: null, required: true }, + \\"co:lon\\": { type: null, required: true }, + \\"semi;colon\\": { type: null, required: true }, + \\"angleets\\": { type: null, required: true }, + \\"equal=sign\\": { type: null, required: true }, + \\"question?mark\\": { type: null, required: true }, + \\"at@sign\\": { type: null, required: true }, + \\"square[brack]ets\\": { type: null, required: true }, + \\"back\\\\\\\\slash\\": { type: null, required: true }, + \\"ca^ret\\": { type: null, required: true }, + \\"back\`tick\\": { type: null, required: true }, + \\"curly{bra}ces\\": { type: null, required: true }, + \\"pi|pe\\": { type: null, required: true }, + \\"til~de\\": { type: null, required: true }, + \\"da-sh\\": { type: null, required: true } + }, + setup(__props: any, { expose: __expose }) { + __expose(); + + + return { } } @@ -232,6 +277,7 @@ export default /*#__PURE__*/_defineComponent({ alias: { type: Array, required: true }, method: { type: Function, required: true }, symbol: { type: Symbol, required: true }, + error: { type: Error, required: true }, extract: { type: Number, required: true }, exclude: { type: [Number, Boolean], required: true }, uppercase: { type: String, required: true }, diff --git a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts index 13b981db3..0d1a41e0f 100644 --- a/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/defineEmits.spec.ts @@ -80,6 +80,18 @@ const emit = defineEmits(['a', 'b']) expect(content).toMatch(`emits: ["foo", "bar"]`) }) + test('w/ type (interface w/ extends)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`emits: ["bar", "foo"]`) + }) + test('w/ type (exported interface)', () => { const { content } = compile(` `) + assertCode(content) + expect(content).toMatch(`"spa ce": { type: null, required: true }`) + expect(content).toMatch( + `"exclamation!mark": { type: null, required: true }` + ) + expect(content).toMatch(`"double\\"quote": { type: null, required: true }`) + expect(content).toMatch(`"hash#tag": { type: null, required: true }`) + expect(content).toMatch(`"dollar$sign": { type: null, required: true }`) + expect(content).toMatch(`"percentage%sign": { type: null, required: true }`) + expect(content).toMatch(`"amper&sand": { type: null, required: true }`) + expect(content).toMatch(`"single'quote": { type: null, required: true }`) + expect(content).toMatch(`"round(brack)ets": { type: null, required: true }`) + expect(content).toMatch(`"aste*risk": { type: null, required: true }`) + expect(content).toMatch(`"pl+us": { type: null, required: true }`) + expect(content).toMatch(`"com,ma": { type: null, required: true }`) + expect(content).toMatch(`"do.t": { type: null, required: true }`) + expect(content).toMatch(`"sla/sh": { type: null, required: true }`) + expect(content).toMatch(`"co:lon": { type: null, required: true }`) + expect(content).toMatch(`"semi;colon": { type: null, required: true }`) + expect(content).toMatch(`"angleets": { type: null, required: true }`) + expect(content).toMatch(`"equal=sign": { type: null, required: true }`) + expect(content).toMatch(`"question?mark": { type: null, required: true }`) + expect(content).toMatch(`"at@sign": { type: null, required: true }`) + expect(content).toMatch( + `"square[brack]ets": { type: null, required: true }` + ) + expect(content).toMatch(`"back\\\\slash": { type: null, required: true }`) + expect(content).toMatch(`"ca^ret": { type: null, required: true }`) + expect(content).toMatch(`"back\`tick": { type: null, required: true }`) + expect(content).toMatch(`"curly{bra}ces": { type: null, required: true }`) + expect(content).toMatch(`"pi|pe": { type: null, required: true }`) + expect(content).toMatch(`"til~de": { type: null, required: true }`) + expect(content).toMatch(`"da-sh": { type: null, required: true }`) + expect(bindings).toStrictEqual({ + 'spa ce': BindingTypes.PROPS, + 'exclamation!mark': BindingTypes.PROPS, + 'double"quote': BindingTypes.PROPS, + 'hash#tag': BindingTypes.PROPS, + dollar$sign: BindingTypes.PROPS, + 'percentage%sign': BindingTypes.PROPS, + 'amper&sand': BindingTypes.PROPS, + "single'quote": BindingTypes.PROPS, + 'round(brack)ets': BindingTypes.PROPS, + 'aste*risk': BindingTypes.PROPS, + 'pl+us': BindingTypes.PROPS, + 'com,ma': BindingTypes.PROPS, + 'do.t': BindingTypes.PROPS, + 'sla/sh': BindingTypes.PROPS, + 'co:lon': BindingTypes.PROPS, + 'semi;colon': BindingTypes.PROPS, + 'angleets': BindingTypes.PROPS, + 'equal=sign': BindingTypes.PROPS, + 'question?mark': BindingTypes.PROPS, + 'at@sign': BindingTypes.PROPS, + 'square[brack]ets': BindingTypes.PROPS, + 'back\\slash': BindingTypes.PROPS, + 'ca^ret': BindingTypes.PROPS, + 'back`tick': BindingTypes.PROPS, + 'curly{bra}ces': BindingTypes.PROPS, + 'pi|pe': BindingTypes.PROPS, + 'til~de': BindingTypes.PROPS, + 'da-sh': BindingTypes.PROPS + }) + }) }) diff --git a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts index fc600f1a5..5f421708a 100644 --- a/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts @@ -481,25 +481,28 @@ describe('resolveType', () => { test.runIf(process.platform === 'win32')('relative ts on Windows', () => { const files = { - 'C:\\Test\\foo.ts': 'export type P = { foo: number }', - 'C:\\Test\\bar.d.ts': + 'C:\\Test\\FolderA\\foo.ts': 'export type P = { foo: number }', + 'C:\\Test\\FolderA\\bar.d.ts': 'type X = { bar: string }; export { X as Y };' + // verify that we can parse syntax that is only valid in d.ts - 'export const baz: boolean' + 'export const baz: boolean', + 'C:\\Test\\FolderB\\buz.ts': 'export type Z = { buz: string }' } const { props, deps } = resolve( ` import { P } from './foo' import { Y as PP } from './bar' - defineProps

() + import { Z as PPP } from '../FolderB/buz' + defineProps

() `, files, {}, - 'C:\\Test\\Test.vue' + 'C:\\Test\\FolderA\\Test.vue' ) expect(props).toStrictEqual({ foo: ['Number'], - bar: ['String'] + bar: ['String'], + buz: ['String'] }) expect(deps && [...deps].map(normalize)).toStrictEqual( Object.keys(files).map(normalize) diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 6c28d1c7f..b253078b8 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -32,27 +32,27 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme", "dependencies": { - "@babel/parser": "^7.23.0", - "@vue/compiler-core": "3.4.0-alpha.1", - "@vue/compiler-dom": "3.4.0-alpha.1", - "@vue/compiler-ssr": "3.4.0-alpha.1", - "@vue/reactivity-transform": "3.4.0-alpha.1", - "@vue/shared": "3.4.0-alpha.1", + "@babel/parser": "^7.23.3", + "@vue/compiler-core": "workspace:*", + "@vue/compiler-dom": "workspace:*", + "@vue/compiler-ssr": "workspace:*", + "@vue/reactivity-transform": "workspace:*", + "@vue/shared": "workspace:*", "estree-walker": "^2.0.2", "magic-string": "^0.30.5", "postcss": "^8.4.31", "source-map-js": "^1.0.2" }, "devDependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.3", "@vue/consolidate": "^0.17.3", "hash-sum": "^2.0.0", - "lru-cache": "^10.0.1", + "lru-cache": "^10.0.3", "merge-source-map": "^1.1.0", "minimatch": "^9.0.3", "postcss-modules": "^4.3.1", "postcss-selector-parser": "^6.0.13", "pug": "^3.0.2", - "sass": "^1.69.4" + "sass": "^1.69.5" } } diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 2a33f6993..9a05d3b32 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -1172,8 +1172,8 @@ function walkObjectPattern( const type = isDefineCall ? BindingTypes.SETUP_CONST : isConst - ? BindingTypes.SETUP_MAYBE_REF - : BindingTypes.SETUP_LET + ? BindingTypes.SETUP_MAYBE_REF + : BindingTypes.SETUP_LET registerBinding(bindings, p.key, type) } else { walkPattern(p.value, bindings, isConst, isDefineCall) @@ -1208,8 +1208,8 @@ function walkPattern( const type = isDefineCall ? BindingTypes.SETUP_CONST : isConst - ? BindingTypes.SETUP_MAYBE_REF - : BindingTypes.SETUP_LET + ? BindingTypes.SETUP_MAYBE_REF + : BindingTypes.SETUP_LET registerBinding(bindings, node, type) } else if (node.type === 'RestElement') { // argument can only be identifier when destructuring @@ -1224,8 +1224,8 @@ function walkPattern( const type = isDefineCall ? BindingTypes.SETUP_CONST : isConst - ? BindingTypes.SETUP_MAYBE_REF - : BindingTypes.SETUP_LET + ? BindingTypes.SETUP_MAYBE_REF + : BindingTypes.SETUP_LET registerBinding(bindings, node.left, type) } else { walkPattern(node.left, bindings, isConst) diff --git a/packages/compiler-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts index b036619c7..8b1adea8e 100644 --- a/packages/compiler-sfc/src/compileTemplate.ts +++ b/packages/compiler-sfc/src/compileTemplate.ts @@ -124,8 +124,8 @@ export function compileTemplate( ? preprocessCustomRequire ? preprocessCustomRequire(preprocessLang) : __ESM_BROWSER__ - ? undefined - : consolidate[preprocessLang as keyof typeof consolidate] + ? undefined + : consolidate[preprocessLang as keyof typeof consolidate] : false if (preprocessor) { try { diff --git a/packages/compiler-sfc/src/index.ts b/packages/compiler-sfc/src/index.ts index c6ee60414..815af3538 100644 --- a/packages/compiler-sfc/src/index.ts +++ b/packages/compiler-sfc/src/index.ts @@ -1,13 +1,17 @@ export const version = __VERSION__ // API -export { parse, parseCache } from './parse' +export { parse } from './parse' export { compileTemplate } from './compileTemplate' export { compileStyle, compileStyleAsync } from './compileStyle' export { compileScript } from './compileScript' export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault' export { resolveTypeElements, inferRuntimeType } from './script/resolveType' +import { SFCParseResult, parseCache as _parseCache } from './parse' +// #9521 export parseCache as a simple map to avoid exposing LRU types +export const parseCache = _parseCache as Map + // TODO remove in 3.4 export { shouldTransform as shouldTransformRef, diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 692eab3ab..b05b8d910 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -164,7 +164,7 @@ export function resolveParserPlugins( } if (lang === 'ts' || lang === 'tsx') { plugins.push(['typescript', { dts }]) - if (!plugins.includes('decorators')) { + if (!userPlugins || !userPlugins.includes('decorators')) { plugins.push('decorators-legacy') } } diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index 449ed250d..c65c91c4f 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -21,7 +21,7 @@ import { isCallOf, unwrapTSNode, toRuntimeTypeString, - getEscapedKey + getEscapedPropName } from './utils' import { genModelProps } from './defineModel' import { getObjectOrArrayExpressionKeys } from './analyzeScriptBindings' @@ -139,7 +139,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { const defaults: string[] = [] for (const key in ctx.propsDestructuredBindings) { const d = genDestructuredDefaultValue(ctx, key) - const finalKey = getEscapedKey(key) + const finalKey = getEscapedPropName(key) if (d) defaults.push( `${finalKey}: ${d.valueString}${ @@ -257,7 +257,7 @@ function genRuntimePropFromType( } } - const finalKey = getEscapedKey(key) + const finalKey = getEscapedPropName(key) if (!ctx.options.isProd) { return `${finalKey}: { ${concatStrings([ `type: ${toRuntimeTypeString(type)}`, diff --git a/packages/compiler-sfc/src/script/importUsageCheck.ts b/packages/compiler-sfc/src/script/importUsageCheck.ts index f3c3932d8..34e95f191 100644 --- a/packages/compiler-sfc/src/script/importUsageCheck.ts +++ b/packages/compiler-sfc/src/script/importUsageCheck.ts @@ -4,6 +4,7 @@ import { NodeTypes, SimpleExpressionNode, createRoot, + forAliasRE, parserOptions, transform, walkIdentifiers @@ -50,12 +51,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { if (!isBuiltInDirective(prop.name)) { code += `,v${capitalize(camelize(prop.name))}` } + + // process dynamic directive arguments if (prop.arg && !(prop.arg as SimpleExpressionNode).isStatic) { - code += `,${processExp( - (prop.arg as SimpleExpressionNode).content, - prop.name + code += `,${stripStrings( + (prop.arg as SimpleExpressionNode).content )}` } + if (prop.exp) { code += `,${processExp( (prop.exp as SimpleExpressionNode).content, @@ -85,8 +88,6 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { return code } -const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ - function processExp(exp: string, dir?: string): string { if (/ as\s+\w|<.*>|:/.test(exp)) { if (dir === 'slot') { diff --git a/packages/compiler-sfc/src/script/resolveType.ts b/packages/compiler-sfc/src/script/resolveType.ts index 12666341e..9fde5e97e 100644 --- a/packages/compiler-sfc/src/script/resolveType.ts +++ b/packages/compiler-sfc/src/script/resolveType.ts @@ -39,8 +39,9 @@ import { parse as babelParse } from '@babel/parser' import { parse } from '../parse' import { createCache } from '../cache' import type TS from 'typescript' -import { extname, dirname } from 'path' +import { extname, dirname, join } from 'path' import { minimatch as isMatch } from 'minimatch' +import * as process from 'process' export type SimpleTypeResolveOptions = Partial< Pick< @@ -356,12 +357,15 @@ function resolveInterfaceMembers( continue } try { - const { props } = resolveTypeElements(ctx, ext, scope) + const { props, calls } = resolveTypeElements(ctx, ext, scope) for (const key in props) { if (!hasOwn(base.props, key)) { base.props[key] = props[key] } } + if (calls) { + ;(base.calls || (base.calls = [])).push(...calls) + } } catch (e) { ctx.error( `Failed to resolve extends base type.\nIf this previously worked in 3.2, ` + @@ -653,8 +657,8 @@ function innerResolveTypeReference( ? scope.exportedDeclares : scope.declares : onlyExported - ? scope.exportedTypes - : scope.types + ? scope.exportedTypes + : scope.types if (lookupSource[name]) { return lookupSource[name] } else { @@ -697,10 +701,10 @@ function getReferenceName(node: ReferenceTypes): string | string[] { node.type === 'TSTypeReference' ? node.typeName : node.type === 'TSExpressionWithTypeArguments' - ? node.expression - : node.type === 'TSImportType' - ? node.qualifier - : node.exprName + ? node.expression + : node.type === 'TSImportType' + ? node.qualifier + : node.exprName if (ref?.type === 'Identifier') { return ref.name } else if (ref?.type === 'TSQualifiedName') { @@ -798,7 +802,12 @@ function importSourceToScope( let resolved: string | undefined = scope.resolvedImportSources[source] if (!resolved) { - if (source.startsWith('.')) { + if (source.startsWith('..')) { + const osSpecificJoinFn = process.platform === 'win32' ? join : joinPaths + + const filename = osSpecificJoinFn(dirname(scope.filename), source) + resolved = resolveExt(filename, fs) + } else if (source.startsWith('.')) { // relative import - fast path const filename = joinPaths(dirname(scope.filename), source) resolved = resolveExt(filename, fs) @@ -1069,8 +1078,8 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope { 'ast' in ctx ? ctx.ast : ctx.scriptAst - ? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body] - : ctx.scriptSetupAst!.body + ? [...ctx.scriptAst.body, ...ctx.scriptSetupAst!.body] + : ctx.scriptSetupAst!.body const scope = new TypeScope( ctx.filename, @@ -1413,6 +1422,7 @@ export function inferRuntimeType( case 'WeakMap': case 'Date': case 'Promise': + case 'Error': return [node.typeName.name] // TS built-in utility types diff --git a/packages/compiler-sfc/src/script/utils.ts b/packages/compiler-sfc/src/script/utils.ts index 42c4718e3..3cbc27026 100644 --- a/packages/compiler-sfc/src/script/utils.ts +++ b/packages/compiler-sfc/src/script/utils.ts @@ -76,8 +76,8 @@ export function getId(node: Expression) { return node.type === 'Identifier' ? node.name : node.type === 'StringLiteral' - ? node.value - : null + ? node.value + : null } const identity = (str: string) => str @@ -113,8 +113,14 @@ export const joinPaths = (path.posix || path).join * key may contain symbols * e.g. onUpdate:modelValue -> "onUpdate:modelValue" */ -export const escapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g +export const propNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~\-]/ -export function getEscapedKey(key: string) { - return escapeSymbolsRE.test(key) ? JSON.stringify(key) : key +export function getEscapedPropName(key: string) { + return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key +} + +export const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g + +export function getEscapedCssVarName(key: string) { + return key.replace(cssVarNameEscapeSymbolsRE, s => `\\${s}`) } diff --git a/packages/compiler-sfc/src/style/cssVars.ts b/packages/compiler-sfc/src/style/cssVars.ts index 2380959b8..9fe727bc5 100644 --- a/packages/compiler-sfc/src/style/cssVars.ts +++ b/packages/compiler-sfc/src/style/cssVars.ts @@ -8,7 +8,7 @@ import { BindingMetadata } from '@vue/compiler-dom' import { SFCDescriptor } from '../parse' -import { escapeSymbolsRE } from '../script/utils' +import { getEscapedCssVarName } from '../script/utils' import { PluginCreator } from 'postcss' import hash from 'hash-sum' @@ -32,7 +32,7 @@ function genVarName(id: string, raw: string, isProd: boolean): string { return hash(id + raw) } else { // escape ASCII Punctuation & Symbols - return `${id}-${raw.replace(escapeSymbolsRE, s => `\\${s}`)}` + return `${id}-${getEscapedCssVarName(raw)}` } } diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index df467affd..28f185f4f 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme", "dependencies": { - "@vue/shared": "3.4.0-alpha.1", - "@vue/compiler-dom": "3.4.0-alpha.1" + "@vue/shared": "workspace:*", + "@vue/compiler-dom": "workspace:*" } } diff --git a/packages/compiler-ssr/src/index.ts b/packages/compiler-ssr/src/index.ts index 1775af52b..1e5f90550 100644 --- a/packages/compiler-ssr/src/index.ts +++ b/packages/compiler-ssr/src/index.ts @@ -72,7 +72,7 @@ export function compile( // reusing core v-bind bind: transformBind, on: transformOn, - // model and show has dedicated SSR handling + // model and show have dedicated SSR handling model: ssrTransformModel, show: ssrTransformShow, // the following are ignored during SSR diff --git a/packages/dts-built-test/package.json b/packages/dts-built-test/package.json index 0a5447877..dca0f80fa 100644 --- a/packages/dts-built-test/package.json +++ b/packages/dts-built-test/package.json @@ -1,11 +1,11 @@ { "name": "@vue/dts-built-test", "private": true, + "version": "0.0.0", "types": "dist/dts-built-test.d.ts", "dependencies": { "@vue/shared": "workspace:*", "@vue/reactivity": "workspace:*", "vue": "workspace:*" - }, - "version": "3.4.0-alpha.1" + } } diff --git a/packages/dts-test/defineComponent.test-d.tsx b/packages/dts-test/defineComponent.test-d.tsx index 7466249e1..b3f735dda 100644 --- a/packages/dts-test/defineComponent.test-d.tsx +++ b/packages/dts-test/defineComponent.test-d.tsx @@ -1472,6 +1472,31 @@ describe('slots', () => { expectType(new comp2().$slots) }) +// #5885 +describe('should work when props type is incompatible with setup returned type ', () => { + type SizeType = 'small' | 'big' + const Comp = defineComponent({ + props: { + size: { + type: String as PropType, + required: true + } + }, + setup(props) { + expectType(props.size) + return { + size: 1 + } + } + }) + type CompInstance = InstanceType + + const CompA = {} as CompInstance + expectType(CompA) + expectType(CompA.size) + expectType(CompA.$props.size) +}) + import { DefineComponent, ComponentOptionsMixin, diff --git a/packages/dts-test/defineCustomElement.test-d.ts b/packages/dts-test/defineCustomElement.test-d.ts index 4e7cf2283..f4f238641 100644 --- a/packages/dts-test/defineCustomElement.test-d.ts +++ b/packages/dts-test/defineCustomElement.test-d.ts @@ -1,5 +1,9 @@ -import { defineCustomElement } from 'vue' -import { expectType, describe } from './utils' +import { + defineCustomElement, + defineComponent, + type VueElementConstructor +} from 'vue' +import { expectType, describe, test } from './utils' describe('inject', () => { // with object inject @@ -62,3 +66,20 @@ describe('inject', () => { } }) }) + +describe('defineCustomElement using defineComponent return type', () => { + test('with emits', () => { + const Comp1Vue = defineComponent({ + props: { + a: String + }, + emits: { + click: () => true + } + }) + const Comp = defineCustomElement(Comp1Vue) + expectType(Comp) + + expectType(new Comp().a) + }) +}) diff --git a/packages/dts-test/package.json b/packages/dts-test/package.json index ff3c8313e..a6ea96bfa 100644 --- a/packages/dts-test/package.json +++ b/packages/dts-test/package.json @@ -1,9 +1,9 @@ { "name": "dts-test", "private": true, + "version": "0.0.0", "dependencies": { "vue": "workspace:*", "@vue/dts-built-test": "workspace:*" - }, - "version": "3.4.0-alpha.1" + } } diff --git a/packages/dts-test/ref.test-d.ts b/packages/dts-test/ref.test-d.ts index bbcde45dd..542d9d6a9 100644 --- a/packages/dts-test/ref.test-d.ts +++ b/packages/dts-test/ref.test-d.ts @@ -15,9 +15,10 @@ import { MaybeRef, MaybeRefOrGetter, ComputedRef, - computed + computed, + ShallowRef } from 'vue' -import { expectType, describe } from './utils' +import { expectType, describe, IsUnion } from './utils' function plainType(arg: number | Ref) { // ref coercing @@ -174,6 +175,27 @@ if (refStatus.value === 'initial') { refStatus.value = 'invalidating' } +{ + const shallow = shallowRef(1) + expectType>(shallow) + expectType>(shallow) +} + +{ + //#7852 + type Steps = { step: '1' } | { step: '2' } + const shallowUnionGenParam = shallowRef({ step: '1' }) + const shallowUnionAsCast = shallowRef({ step: '1' } as Steps) + + expectType>(false) + expectType>(false) +} + +describe('shallowRef with generic', () => { + const r = ref({}) as MaybeRef + expectType | Ref>(shallowRef(r)) +}) + // proxyRefs: should return `reactive` directly const r1 = reactive({ k: 'v' diff --git a/packages/dts-test/setupHelpers.test-d.ts b/packages/dts-test/setupHelpers.test-d.ts index feb4085de..51f95c009 100644 --- a/packages/dts-test/setupHelpers.test-d.ts +++ b/packages/dts-test/setupHelpers.test-d.ts @@ -8,7 +8,8 @@ import { defineSlots, VNode, Ref, - defineModel + defineModel, + toRefs } from 'vue' import { describe, expectType } from './utils' import { defineComponent } from 'vue' @@ -20,6 +21,7 @@ describe('defineProps w/ type declaration', () => { foo: string bool?: boolean boolAndUndefined: boolean | undefined + file?: File | File[] }>() // explicitly declared type should be refined expectType(props.foo) @@ -108,6 +110,7 @@ describe('defineProps w/ generic type declaration + withDefaults', (res.generic1) expectType<{ x: T }>(res.generic2) @@ -328,3 +335,11 @@ describe('useSlots', () => { const slots = useSlots() expectType(slots) }) + +// #6420 +describe('toRefs w/ type declaration', () => { + const props = defineProps<{ + file?: File | File[] + }>() + expectType>(toRefs(props).file) +}) diff --git a/packages/dts-test/tsx.test-d.tsx b/packages/dts-test/tsx.test-d.tsx index 04915a967..4b4a0dbf9 100644 --- a/packages/dts-test/tsx.test-d.tsx +++ b/packages/dts-test/tsx.test-d.tsx @@ -17,6 +17,59 @@ expectType(

) +// allow undefined, string, object, array and nested array classes +expectType(
) +expectType(
) +expectType(
) +expectType(
) +expectType(
) +expectType(
) +expectType(
) +expectType( +
+) +expectType( +
+) +expectType( +
+) + +// #7955 +expectType(
) + +expectType(
) + +expectType(
) + +expectType(
) + +expectType(
) + +// @ts-expect-error +;
+ +// @ts-expect-error +;
+ // @ts-expect-error unknown prop ;
diff --git a/packages/dts-test/watch.test-d.ts b/packages/dts-test/watch.test-d.ts index 9d727999b..cc631d37a 100644 --- a/packages/dts-test/watch.test-d.ts +++ b/packages/dts-test/watch.test-d.ts @@ -1,4 +1,4 @@ -import { ref, computed, watch, defineComponent } from 'vue' +import { ref, computed, watch, defineComponent, shallowRef } from 'vue' import { expectType } from './utils' const source = ref('foo') @@ -92,3 +92,17 @@ defineComponent({ ) } }) + +{ + //#7852 + type Steps = { step: '1' } | { step: '2' } + const shallowUnionGenParam = shallowRef({ step: '1' }) + const shallowUnionAsCast = shallowRef({ step: '1' } as Steps) + + watch(shallowUnionGenParam, value => { + expectType(value) + }) + watch(shallowUnionAsCast, value => { + expectType(value) + }) +} diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json index 8e43685c5..d84e4bdd9 100644 --- a/packages/reactivity-transform/package.json +++ b/packages/reactivity-transform/package.json @@ -28,14 +28,14 @@ }, "homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme", "dependencies": { - "@babel/parser": "^7.23.0", - "@vue/compiler-core": "3.4.0-alpha.1", - "@vue/shared": "3.4.0-alpha.1", + "@babel/parser": "^7.23.3", + "@vue/compiler-core": "workspace:*", + "@vue/shared": "workspace:*", "estree-walker": "^2.0.2", "magic-string": "^0.30.5" }, "devDependencies": { - "@babel/core": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/core": "^7.23.3", + "@babel/types": "^7.23.3" } } diff --git a/packages/reactivity-transform/src/reactivityTransform.ts b/packages/reactivity-transform/src/reactivityTransform.ts index 855dfbb18..596de0e12 100644 --- a/packages/reactivity-transform/src/reactivityTransform.ts +++ b/packages/reactivity-transform/src/reactivityTransform.ts @@ -448,8 +448,8 @@ export function transformAST( const keyStr = isString(key) ? `'${key}'` : key - ? snip(key) - : `'${nameId.name}'` + ? snip(key) + : `'${nameId.name}'` const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : `` s.appendLeft( call.end! + offset, diff --git a/packages/reactivity/__tests__/readonly.spec.ts b/packages/reactivity/__tests__/readonly.spec.ts index d0c91f0fb..448419d31 100644 --- a/packages/reactivity/__tests__/readonly.spec.ts +++ b/packages/reactivity/__tests__/readonly.spec.ts @@ -275,6 +275,14 @@ describe('reactivity/readonly', () => { expect(isReactive(value)).toBe(true) } }) + + test('should return undefined from Map.clear() call', () => { + const wrapped = readonly(new Collection()) + expect(wrapped.clear()).toBeUndefined() + expect( + `Clear operation failed: target is readonly.` + ).toHaveBeenWarned() + }) } }) }) @@ -332,6 +340,14 @@ describe('reactivity/readonly', () => { expect(isReadonly(v2)).toBe(true) } }) + + test('should return undefined from Set.clear() call', () => { + const wrapped = readonly(new Collection()) + expect(wrapped.clear()).toBeUndefined() + expect( + `Clear operation failed: target is readonly.` + ).toHaveBeenWarned() + }) } }) }) diff --git a/packages/reactivity/__tests__/shallowReadonly.spec.ts b/packages/reactivity/__tests__/shallowReadonly.spec.ts index 79d4376cc..b6736f4a5 100644 --- a/packages/reactivity/__tests__/shallowReadonly.spec.ts +++ b/packages/reactivity/__tests__/shallowReadonly.spec.ts @@ -113,6 +113,12 @@ describe('reactivity/shallowReadonly', () => { ).not.toHaveBeenWarned() }) }) + + test('should return undefined from Map.clear() call', () => { + const sroMap = shallowReadonly(new Map()) + expect(sroMap.clear()).toBeUndefined() + expect(`Clear operation failed: target is readonly.`).toHaveBeenWarned() + }) }) describe('collection/Set', () => { @@ -197,5 +203,11 @@ describe('reactivity/shallowReadonly', () => { ).not.toHaveBeenWarned() }) }) + + test('should return undefined from Set.clear() call', () => { + const sroSet = shallowReadonly(new Set()) + expect(sroSet.clear()).toBeUndefined() + expect(`Clear operation failed: target is readonly.`).toHaveBeenWarned() + }) }) }) diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index 7244b9a92..28647fd55 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -36,6 +36,6 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme", "dependencies": { - "@vue/shared": "3.4.0-alpha.1" + "@vue/shared": "workspace:*" } } diff --git a/packages/reactivity/src/baseHandlers.ts b/packages/reactivity/src/baseHandlers.ts index 36e4d311b..d365b6733 100644 --- a/packages/reactivity/src/baseHandlers.ts +++ b/packages/reactivity/src/baseHandlers.ts @@ -109,8 +109,8 @@ class BaseReactiveHandler implements ProxyHandler { ? shallowReadonlyMap : readonlyMap : shallow - ? shallowReactiveMap - : reactiveMap + ? shallowReactiveMap + : reactiveMap ).get(target) ) { return target diff --git a/packages/reactivity/src/collectionHandlers.ts b/packages/reactivity/src/collectionHandlers.ts index e8d99840f..9d7c1dd48 100644 --- a/packages/reactivity/src/collectionHandlers.ts +++ b/packages/reactivity/src/collectionHandlers.ts @@ -228,7 +228,11 @@ function createReadonlyMethod(type: TriggerOpTypes): Function { toRaw(this) ) } - return type === TriggerOpTypes.DELETE ? false : this + return type === TriggerOpTypes.DELETE + ? false + : type === TriggerOpTypes.CLEAR + ? undefined + : this } } @@ -342,8 +346,8 @@ function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) { ? shallowReadonlyInstrumentations : shallowInstrumentations : isReadonly - ? readonlyInstrumentations - : mutableInstrumentations + ? readonlyInstrumentations + : mutableInstrumentations return ( target: CollectionTypes, diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index 2904c69ab..c4888f6fb 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -138,24 +138,24 @@ type Builtin = Primitive | Function | Date | Error | RegExp export type DeepReadonly = T extends Builtin ? T : T extends Map - ? ReadonlyMap, DeepReadonly> - : T extends ReadonlyMap - ? ReadonlyMap, DeepReadonly> - : T extends WeakMap - ? WeakMap, DeepReadonly> - : T extends Set - ? ReadonlySet> - : T extends ReadonlySet - ? ReadonlySet> - : T extends WeakSet - ? WeakSet> - : T extends Promise - ? Promise> - : T extends Ref - ? Readonly>> - : T extends {} - ? { readonly [K in keyof T]: DeepReadonly } - : Readonly + ? ReadonlyMap, DeepReadonly> + : T extends ReadonlyMap + ? ReadonlyMap, DeepReadonly> + : T extends WeakMap + ? WeakMap, DeepReadonly> + : T extends Set + ? ReadonlySet> + : T extends ReadonlySet + ? ReadonlySet> + : T extends WeakSet + ? WeakSet> + : T extends Promise + ? Promise> + : T extends Ref + ? Readonly>> + : T extends {} + ? { readonly [K in keyof T]: DeepReadonly } + : Readonly /** * Takes an object (reactive or plain) or a ref and returns a readonly proxy to diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 5a4dd710e..705d442dc 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,3 +1,4 @@ +import type { ComputedRef } from './computed' import { activeEffect, shouldTrack, @@ -128,9 +129,8 @@ export type ShallowRef = Ref & { [ShallowRefMarker]?: true } * @param value - The "inner value" for the shallow ref. * @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref} */ -export function shallowRef( - value: T -): T extends Ref ? T : ShallowRef +export function shallowRef(value: MaybeRef): Ref | ShallowRef +export function shallowRef(value: T): T export function shallowRef(value: T): ShallowRef export function shallowRef(): ShallowRef export function shallowRef(value?: unknown) { @@ -224,7 +224,7 @@ export type MaybeRefOrGetter = MaybeRef | (() => T) * @param ref - Ref or plain value to be converted into the plain value. * @see {@link https://vuejs.org/api/reactivity-utilities.html#unref} */ -export function unref(ref: MaybeRef): T { +export function unref(ref: MaybeRef | ComputedRef): T { return isRef(ref) ? ref.value : ref } @@ -244,7 +244,7 @@ export function unref(ref: MaybeRef): T { * @param source - A getter, an existing ref, or a non-function value. * @see {@link https://vuejs.org/api/reactivity-utilities.html#tovalue} */ -export function toValue(source: MaybeRefOrGetter): T { +export function toValue(source: MaybeRefOrGetter | ComputedRef): T { return isFunction(source) ? source() : unref(source) } @@ -429,8 +429,8 @@ export function toRef( ): T extends () => infer R ? Readonly> : T extends Ref - ? T - : Ref> + ? T + : Ref> export function toRef( object: T, key: K @@ -491,17 +491,17 @@ export type ShallowUnwrapRef = { [K in keyof T]: T[K] extends Ref ? V // if `V` is `unknown` that means it does not extend `Ref` and is undefined : T[K] extends Ref | undefined - ? unknown extends V - ? undefined - : V | undefined - : T[K] + ? unknown extends V + ? undefined + : V | undefined + : T[K] } export type UnwrapRef = T extends ShallowRef ? V : T extends Ref - ? UnwrapRefSimple - : UnwrapRefSimple + ? UnwrapRefSimple + : UnwrapRefSimple export type UnwrapRefSimple = T extends | Function @@ -512,9 +512,9 @@ export type UnwrapRefSimple = T extends | { [RawSymbol]?: true } ? T : T extends ReadonlyArray - ? { [K in keyof T]: UnwrapRefSimple } - : T extends object & { [ShallowReactiveMarker]?: never } - ? { - [P in keyof T]: P extends symbol ? T[P] : UnwrapRef - } - : T + ? { [K in keyof T]: UnwrapRefSimple } + : T extends object & { [ShallowReactiveMarker]?: never } + ? { + [P in keyof T]: P extends symbol ? T[P] : UnwrapRef + } + : T diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index bddfc5ff5..1bc012bb3 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -91,7 +91,7 @@ describe('api: watch', () => { array.push(1) await nextTick() expect(spy).toBeCalledTimes(1) - expect(spy).toBeCalledWith([1], expect.anything(), expect.anything()) + expect(spy).toBeCalledWith([1], [1], expect.anything()) }) it('should not fire if watched getter result did not change', async () => { @@ -1243,4 +1243,39 @@ describe('api: watch', () => { expect(count.value).toBe(2) expect(cb).toHaveBeenCalledTimes(1) }) + + // #5151 + test('OnCleanup also needs to be cleaned,', async () => { + const spy1 = vi.fn() + const spy2 = vi.fn() + const num = ref(0) + + watch(num, (value, oldValue, onCleanup) => { + if (value > 1) { + return + } + spy1() + onCleanup(() => { + // OnCleanup also needs to be cleaned + spy2() + }) + }) + + num.value++ + await nextTick() + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).toHaveBeenCalledTimes(0) + + num.value++ + await nextTick() + + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).toHaveBeenCalledTimes(1) + + num.value++ + await nextTick() + // would not be calld when value>1 + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index 89112f2ad..885e80090 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -336,7 +336,8 @@ describe('component props', () => { obj: { type: Object }, cls: { type: MyClass }, fn: { type: Function }, - skipCheck: { type: [Boolean, Function], skipCheck: true } + skipCheck: { type: [Boolean, Function], skipCheck: true }, + empty: { type: [] } }, setup() { return () => null @@ -351,7 +352,8 @@ describe('component props', () => { obj: 'false', cls: {}, fn: true, - skipCheck: 'foo' + skipCheck: 'foo', + empty: [1, 2, 3] }), nodeOps.createElement('div') ) @@ -379,6 +381,9 @@ describe('component props', () => { expect( `Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".` ).not.toHaveBeenWarned() + expect( + `Prop type [] for prop "empty" won't match anything. Did you mean to use type Array instead?` + ).toHaveBeenWarned() }) // #3495 diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 065898048..d822a9928 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -17,9 +17,12 @@ import { onUnmounted, onErrorCaptured, shallowRef, + SuspenseProps, + resolveDynamicComponent, Fragment } from '@vue/runtime-test' import { createApp, defineComponent } from 'vue' +import { type RawSlots } from 'packages/runtime-core/src/componentSlots' describe('Suspense', () => { const deps: Promise[] = [] @@ -1523,4 +1526,75 @@ describe('Suspense', () => { expected = `
outerB
innerB
` expect(serializeInner(root)).toBe(expected) }) + + describe('warnings', () => { + // base function to check if a combination of slots warns or not + function baseCheckWarn( + shouldWarn: boolean, + children: RawSlots, + props: SuspenseProps | null = null + ) { + const Comp = { + setup() { + return () => h(Suspense, props, children) + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + + if (shouldWarn) { + expect(` slots expect a single root node.`).toHaveBeenWarned() + } else { + expect( + ` slots expect a single root node.` + ).not.toHaveBeenWarned() + } + } + + // actual function that we use in tests + const checkWarn = baseCheckWarn.bind(null, true) + const checkNoWarn = baseCheckWarn.bind(null, false) + + test('does not warn on single child', async () => { + checkNoWarn({ + default: h('div'), + fallback: h('div') + }) + }) + + test('does not warn on null', async () => { + checkNoWarn({ + default: null, + fallback: null + }) + }) + + test('does not warn on ', async () => { + checkNoWarn({ + default: () => [resolveDynamicComponent(null)], + fallback: () => null + }) + }) + + test('does not warn on empty array', async () => { + checkNoWarn({ + default: [], + fallback: () => [] + }) + }) + + test('warns on multiple children in default', async () => { + checkWarn({ + default: [h('div'), h('div')] + }) + }) + + test('warns on multiple children in fallback', async () => { + checkWarn({ + default: h('div'), + fallback: [h('div'), h('div')] + }) + }) + }) }) diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts index 2e989e368..a5ec90ad7 100644 --- a/packages/runtime-core/__tests__/hmr.spec.ts +++ b/packages/runtime-core/__tests__/hmr.spec.ts @@ -218,6 +218,75 @@ describe('hot module replacement', () => { expect(deactiveSpy).toHaveBeenCalledTimes(1) }) + // #7121 + test('reload KeepAlive slot in Transition', async () => { + const root = nodeOps.createElement('div') + const childId = 'test-transition-keep-alive-reload' + const unmountSpy = vi.fn() + const mountSpy = vi.fn() + const activeSpy = vi.fn() + const deactiveSpy = vi.fn() + + const Child: ComponentOptions = { + __hmrId: childId, + data() { + return { count: 0 } + }, + unmounted: unmountSpy, + render: compileToFunction(`
{{ count }}
`) + } + createRecord(childId, Child) + + const Parent: ComponentOptions = { + components: { Child }, + data() { + return { toggle: true } + }, + render: compileToFunction( + `` + ) + } + + render(h(Parent), root) + expect(serializeInner(root)).toBe(`
0
`) + + reload(childId, { + __hmrId: childId, + data() { + return { count: 1 } + }, + mounted: mountSpy, + unmounted: unmountSpy, + activated: activeSpy, + deactivated: deactiveSpy, + render: compileToFunction(`
{{ count }}
`) + }) + await nextTick() + expect(serializeInner(root)).toBe(`
1
`) + expect(unmountSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + expect(activeSpy).toHaveBeenCalledTimes(1) + expect(deactiveSpy).toHaveBeenCalledTimes(0) + + // should not unmount when toggling + triggerEvent(root.children[1] as TestElement, 'click') + await nextTick() + expect(serializeInner(root)).toBe(``) + expect(unmountSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + expect(activeSpy).toHaveBeenCalledTimes(1) + expect(deactiveSpy).toHaveBeenCalledTimes(1) + + // should not mount when toggling + triggerEvent(root.children[1] as TestElement, 'click') + await nextTick() + expect(serializeInner(root)).toBe(`
1
`) + expect(unmountSpy).toHaveBeenCalledTimes(1) + expect(mountSpy).toHaveBeenCalledTimes(1) + expect(activeSpy).toHaveBeenCalledTimes(2) + expect(deactiveSpy).toHaveBeenCalledTimes(1) + }) + test('reload class component', async () => { const root = nodeOps.createElement('div') const childId = 'test4-child' diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index 759804b97..7ea607d33 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -935,6 +935,18 @@ describe('SSR hydration', () => { ) }) + test('force hydrate prop with `.prop` modifier', () => { + const { container } = mountWithHydration( + '', + () => + h('input', { + type: 'checkbox', + '.indeterminate': true + }) + ) + expect((container.firstChild! as any).indeterminate).toBe(true) + }) + test('force hydrate input v-model with non-string value bindings', () => { const { container } = mountWithHydration( '', @@ -953,6 +965,20 @@ describe('SSR hydration', () => { expect((container.firstChild as any)._trueValue).toBe(true) }) + test('force hydrate checkbox with indeterminate', () => { + const { container } = mountWithHydration( + '', + () => + createVNode( + 'input', + { type: 'checkbox', indeterminate: '' }, + null, + PatchFlags.HOISTED + ) + ) + expect((container.firstChild as any).indeterminate).toBe(true) + }) + test('force hydrate select option with non-string value bindings', () => { const { container } = mountWithHydration( '', @@ -1177,5 +1203,21 @@ describe('SSR hydration', () => { expect(teleportContainer.innerHTML).toBe(`value`) expect(`Hydration children mismatch`).toHaveBeenWarned() }) + + test('comment mismatch (element)', () => { + const { container } = mountWithHydration(`
`, () => + h('div', [createCommentVNode('hi')]) + ) + expect(container.innerHTML).toBe('
') + expect(`Hydration node mismatch`).toHaveBeenWarned() + }) + + test('comment mismatch (text)', () => { + const { container } = mountWithHydration(`
foobar
`, () => + h('div', [createCommentVNode('hi')]) + ) + expect(container.innerHTML).toBe('
') + expect(`Hydration node mismatch`).toHaveBeenWarned() + }) }) }) diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index 6f1d0288b..37daafa97 100644 --- a/packages/runtime-core/__tests__/rendererComponent.spec.ts +++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts @@ -354,4 +354,25 @@ describe('renderer: component', () => { expect(serializeInner(root)).toBe(`

1

`) expect(spy).toHaveBeenCalledTimes(2) }) + + it('should warn accessing `this` in a