From 795e5855ba496bcd921c8885d241c269dd984bee Mon Sep 17 00:00:00 2001 From: Kieran Barker <29986418+kieranbarker@users.noreply.github.com> Date: Tue, 10 Nov 2020 13:13:29 +0000 Subject: [PATCH 01/12] blog: fix link to Svelte package on npm (#5669) --- site/content/blog/2019-04-16-svelte-for-new-developers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/blog/2019-04-16-svelte-for-new-developers.md b/site/content/blog/2019-04-16-svelte-for-new-developers.md index 921e699842..a305f0fcc0 100644 --- a/site/content/blog/2019-04-16-svelte-for-new-developers.md +++ b/site/content/blog/2019-04-16-svelte-for-new-developers.md @@ -45,7 +45,7 @@ A full introduction to the command line is out of the scope of this guide, but h Once installed, you'll have access to three new commands: * `node my-file.js` — runs the JavaScript in `my-file.js` -* `npm [subcommand]` — [npm](https://www.npmjs.com/) is a way to install 'packages' that your application depends on, such as the [svelte](https://www.npmjs.com/) package +* `npm [subcommand]` — [npm](https://www.npmjs.com/) is a way to install 'packages' that your application depends on, such as the [svelte](https://www.npmjs.com/package/svelte) package * `npx [subcommand]` — a convenient way to run programs available on npm without permanently installing them From e5124e404516d858d9aaee31a7c3bb6593609986 Mon Sep 17 00:00:00 2001 From: Fraser Darwent Date: Tue, 10 Nov 2020 15:52:02 +0000 Subject: [PATCH 02/12] add register to exports map (#5671) --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 163a105506..06f2015718 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,9 @@ "import": "./motion/index.mjs", "require": "./motion/index.js" }, + "./register": { + "require": "./register.js" + }, "./store": { "import": "./store/index.mjs", "require": "./store/index.js" From 4524566fdaa0d9471c9d0d1ea85fde89e8829c4d Mon Sep 17 00:00:00 2001 From: Conduitry Date: Tue, 10 Nov 2020 10:53:12 -0500 Subject: [PATCH 03/12] -> v3.29.7 --- CHANGELOG.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ea29fbd2..a9ff0e72de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## 3.29.7 + +* Include `./register` in exports map ([#5670](https://github.com/sveltejs/svelte/issues/5670)) + ## 3.29.6 * Include `./package.json` in export map ([#5659](https://github.com/sveltejs/svelte/issues/5659)) diff --git a/package-lock.json b/package-lock.json index 5937018cb6..95167488ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.6", + "version": "3.29.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 06f2015718..61c213494c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.29.6", + "version": "3.29.7", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 02e560c9dafbbe295d46df4ee9c9c9ffd5d9156c Mon Sep 17 00:00:00 2001 From: Geoff Rich Date: Mon, 16 Nov 2020 10:59:56 -0800 Subject: [PATCH 04/12] docs a11y: increase color contrast in code block syntax highlighting (#5681) --- site/static/prism.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/site/static/prism.css b/site/static/prism.css index c803829288..0bedd0f02b 100644 --- a/site/static/prism.css +++ b/site/static/prism.css @@ -7,12 +7,12 @@ /* colors --------------------------------- */ pre[class*='language-'] { --background: var(--back-light); - --base: hsl(45, 7%, 45%); - --comment: hsl(210, 25%, 60%); - --keyword: hsl(204, 58%, 45%); - --function: hsl(19, 67%, 45%); - --string: hsl(41, 37%, 45%); - --number: hsl(102, 27%, 50%); + --base: #545454; + --comment: #696969; + --keyword: #007f8a; + --function: #bb5525; + --string: #856e3d; + --number: #008000; --tags: var(--function); --important: var(--string); } From e750b7284e2edfde71ef93957c01d243ae57d9ac Mon Sep 17 00:00:00 2001 From: swyx Date: Tue, 17 Nov 2020 18:41:35 +0800 Subject: [PATCH 05/12] document await...catch shorthand (#5682) --- site/content/docs/02-template-syntax.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 119488aacc..ac7ca7abf3 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -300,6 +300,9 @@ An each block can also have an `{:else}` clause, which is rendered if the list i ```sv {#await expression then name}...{/await} ``` +```sv +{#await expression catch name}...{/await} +``` --- @@ -342,6 +345,16 @@ If you don't care about the pending state, you can also omit the initial block. {/await} ``` +--- + +If conversely you only want to show the error state, you can omit the `then` block. + +```sv +{#await promise catch error} +

The error is {error}

+{/await} +``` + ### {#key ...} ```sv From ddd79e3f9d11a73c02beb9bb908e4184d6c818a9 Mon Sep 17 00:00:00 2001 From: fivem Date: Wed, 18 Nov 2020 15:34:44 +0300 Subject: [PATCH 06/12] tutorial: remove unneccessary performance.now() call inside raf (#5513) --- .../tutorial/06-bindings/12-bind-this/app-a/App.svelte | 8 +++----- .../tutorial/06-bindings/12-bind-this/app-b/App.svelte | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte index d40c45ef6c..f8f93edc2b 100644 --- a/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte +++ b/site/content/tutorial/06-bindings/12-bind-this/app-a/App.svelte @@ -5,9 +5,9 @@ onMount(() => { const ctx = canvas.getContext('2d'); - let frame; + let frame = requestAnimationFrame(loop); - (function loop() { + function loop(t) { frame = requestAnimationFrame(loop); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -17,8 +17,6 @@ const x = i % canvas.width; const y = i / canvas.height >>> 0; - const t = window.performance.now(); - const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000)); const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000)); const b = 128; @@ -30,7 +28,7 @@ } ctx.putImageData(imageData, 0, 0); - }()); + } return () => { cancelAnimationFrame(frame); diff --git a/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte b/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte index 8e4b3c5bef..17005e273a 100644 --- a/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte +++ b/site/content/tutorial/06-bindings/12-bind-this/app-b/App.svelte @@ -5,9 +5,9 @@ onMount(() => { const ctx = canvas.getContext('2d'); - let frame; + let frame = requestAnimationFrame(loop); - (function loop() { + function loop(t) { frame = requestAnimationFrame(loop); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -17,8 +17,6 @@ const x = i % canvas.width; const y = i / canvas.height >>> 0; - const t = window.performance.now(); - const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000)); const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000)); const b = 128; @@ -30,7 +28,7 @@ } ctx.putImageData(imageData, 0, 0); - }()); + } return () => { cancelAnimationFrame(frame); From daec25604f16362315389bc660781fa9a2db00b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20=C3=85berg=20Kultalahti?= Date: Wed, 18 Nov 2020 15:41:47 +0100 Subject: [PATCH 07/12] docs: add conditional slot example (#5383) --- .../04-conditional-slots/App.svelte | 13 ++++++++++ .../04-conditional-slots/Profile.svelte | 24 +++++++++++++++++++ .../04-conditional-slots/meta.json | 3 +++ .../{04-modal => 05-modal}/App.svelte | 0 .../{04-modal => 05-modal}/Modal.svelte | 0 .../{04-modal => 05-modal}/meta.json | 0 6 files changed, 40 insertions(+) create mode 100644 site/content/examples/15-composition/04-conditional-slots/App.svelte create mode 100644 site/content/examples/15-composition/04-conditional-slots/Profile.svelte create mode 100644 site/content/examples/15-composition/04-conditional-slots/meta.json rename site/content/examples/15-composition/{04-modal => 05-modal}/App.svelte (100%) rename site/content/examples/15-composition/{04-modal => 05-modal}/Modal.svelte (100%) rename site/content/examples/15-composition/{04-modal => 05-modal}/meta.json (100%) diff --git a/site/content/examples/15-composition/04-conditional-slots/App.svelte b/site/content/examples/15-composition/04-conditional-slots/App.svelte new file mode 100644 index 0000000000..69142b807d --- /dev/null +++ b/site/content/examples/15-composition/04-conditional-slots/App.svelte @@ -0,0 +1,13 @@ + + + + Bob + bob@email.com + + + + Alice + 12345678 + diff --git a/site/content/examples/15-composition/04-conditional-slots/Profile.svelte b/site/content/examples/15-composition/04-conditional-slots/Profile.svelte new file mode 100644 index 0000000000..09026cd6d6 --- /dev/null +++ b/site/content/examples/15-composition/04-conditional-slots/Profile.svelte @@ -0,0 +1,24 @@ + + +
+
Name
+ + {#if $$slots.email} +
Email
+ + {/if} + {#if $$slots.phone} +
Phone
+ + {/if} +
diff --git a/site/content/examples/15-composition/04-conditional-slots/meta.json b/site/content/examples/15-composition/04-conditional-slots/meta.json new file mode 100644 index 0000000000..95809f4c93 --- /dev/null +++ b/site/content/examples/15-composition/04-conditional-slots/meta.json @@ -0,0 +1,3 @@ +{ + "title": "Conditional Slots" +} \ No newline at end of file diff --git a/site/content/examples/15-composition/04-modal/App.svelte b/site/content/examples/15-composition/05-modal/App.svelte similarity index 100% rename from site/content/examples/15-composition/04-modal/App.svelte rename to site/content/examples/15-composition/05-modal/App.svelte diff --git a/site/content/examples/15-composition/04-modal/Modal.svelte b/site/content/examples/15-composition/05-modal/Modal.svelte similarity index 100% rename from site/content/examples/15-composition/04-modal/Modal.svelte rename to site/content/examples/15-composition/05-modal/Modal.svelte diff --git a/site/content/examples/15-composition/04-modal/meta.json b/site/content/examples/15-composition/05-modal/meta.json similarity index 100% rename from site/content/examples/15-composition/04-modal/meta.json rename to site/content/examples/15-composition/05-modal/meta.json From 96ac898370971e5f993e6556bd96fc2ac38139e1 Mon Sep 17 00:00:00 2001 From: Phippsy Date: Thu, 19 Nov 2020 04:24:43 +1100 Subject: [PATCH 08/12] docs: mention that degit requires Git (#5532) --- site/content/blog/2019-04-16-svelte-for-new-developers.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/site/content/blog/2019-04-16-svelte-for-new-developers.md b/site/content/blog/2019-04-16-svelte-for-new-developers.md index a305f0fcc0..76308da547 100644 --- a/site/content/blog/2019-04-16-svelte-for-new-developers.md +++ b/site/content/blog/2019-04-16-svelte-for-new-developers.md @@ -58,9 +58,7 @@ To write code, you need a good editor. The most popular choice is [Visual Studio We're going to follow the instructions in part two of [The easiest way to get started with Svelte](/blog/the-easiest-way-to-get-started). -First, we'll use npx to run [degit](https://github.com/Rich-Harris/degit), a program for cloning project templates from [GitHub](https://github.com) and other code storage websites. You don't have to use a project template, but it means you have to do a lot less setup work. - -(Eventually you'll probably have to learn [git](https://git-scm.com/), which most programmers use to manage their projects. But you don't need to worry about it just yet.) +First, we'll use npx to run [degit](https://github.com/Rich-Harris/degit), a program for cloning project templates from [GitHub](https://github.com) and other code storage websites. You don't have to use a project template, but it means you have to do a lot less setup work. You will need to have [Git](https://git-scm.com/) installed in order to use degit. (Eventually you'll probably have to learn [Git](https://git-scm.com/) itself, which most programmers use to manage their projects.) On the command line, navigate to where you want to create a new project, then type the following lines (you can paste the whole lot, but you'll develop better muscle memory if you get into the habit of writing each line out one at a time then running it): From 449fd1ca1f3e82f2f6d68ca06ffd5d731da197ea Mon Sep 17 00:00:00 2001 From: Antony Jones Date: Wed, 18 Nov 2020 18:40:29 +0000 Subject: [PATCH 09/12] Add envinfo note to github bug template --- .github/ISSUE_TEMPLATE/bug_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 94f0340e8e..2aa885cfcb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -37,6 +37,8 @@ If you have a stack trace to include, we recommend putting inside a `
`
**Information about your Svelte project:** +To make your life easier, just run `npx envinfo --system --npmPackages svelte,rollup,webpack --binaries --browsers` and paste the output here. + - Your browser and the version: (e.x. Chrome 52.1, Firefox 48.0, IE 10) - Your operating system: (e.x. OS X 10, Ubuntu Linux 19.10, Windows XP, etc) From 1fa46fde4f17fd3e9872339b38243e1d220e610c Mon Sep 17 00:00:00 2001 From: pushkin Date: Thu, 19 Nov 2020 00:30:47 +0100 Subject: [PATCH 10/12] Correct `onDestroy` behavior description (#5590) * Update 03-run-time.md * Update site/content/docs/03-run-time.md Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- site/content/docs/03-run-time.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 846e09d774..b35af93838 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -102,7 +102,7 @@ onDestroy(callback: () => void) --- -Schedules a callback to run once the component is unmounted. +Schedules a callback to run immediately before the component is unmounted. Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the only one that runs inside a server-side component. From dcfbd6951634408d97757f2f66cbfbe622d107c2 Mon Sep 17 00:00:00 2001 From: halfnelson Date: Fri, 20 Nov 2020 04:38:41 +1000 Subject: [PATCH 11/12] Preprocessor sourcemap support (#5584) Co-authored-by: Milan Hauth Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- package-lock.json | 22 +- package.json | 2 + src/compiler/compile/Component.ts | 4 + src/compiler/compile/index.ts | 1 + src/compiler/compile/render_dom/index.ts | 5 + src/compiler/interfaces.ts | 1 + src/compiler/preprocess/index.ts | 154 +++++++--- src/compiler/utils/string_with_sourcemap.ts | 275 ++++++++++++++++++ test/preprocess/index.ts | 3 + test/setup.js | 2 +- test/sourcemaps/helpers.ts | 20 ++ test/sourcemaps/index.ts | 8 +- .../samples/compile-option-dev/_config.js | 21 ++ .../samples/compile-option-dev/input.svelte | 15 + .../samples/compile-option-dev/test.js | 40 +++ .../samples/decoded-sourcemap/_config.js | 15 + .../samples/decoded-sourcemap/input.svelte | 2 + .../samples/decoded-sourcemap/test.js | 19 ++ .../samples/preprocessed-markup/_config.js | 12 + .../samples/preprocessed-markup/input.svelte | 5 + .../samples/preprocessed-markup/test.js | 32 ++ .../samples/preprocessed-multiple/_config.js | 25 ++ .../preprocessed-multiple/input.svelte | 9 + .../samples/preprocessed-multiple/test.js | 32 ++ .../samples/preprocessed-script/_config.js | 12 + .../samples/preprocessed-script/input.svelte | 9 + .../samples/preprocessed-script/test.js | 32 ++ .../samples/preprocessed-styles/_config.js | 12 + .../samples/preprocessed-styles/input.svelte | 12 + .../samples/preprocessed-styles/test.js | 32 ++ .../samples/sourcemap-names/_config.js | 33 +++ .../samples/sourcemap-names/input.svelte | 12 + .../samples/sourcemap-names/test.js | 42 +++ .../samples/sourcemap-sources/_config.js | 60 ++++ .../samples/sourcemap-sources/input.svelte | 4 + .../samples/sourcemap-sources/test.js | 29 ++ 36 files changed, 970 insertions(+), 43 deletions(-) create mode 100644 src/compiler/utils/string_with_sourcemap.ts create mode 100644 test/sourcemaps/helpers.ts create mode 100644 test/sourcemaps/samples/compile-option-dev/_config.js create mode 100644 test/sourcemaps/samples/compile-option-dev/input.svelte create mode 100644 test/sourcemaps/samples/compile-option-dev/test.js create mode 100644 test/sourcemaps/samples/decoded-sourcemap/_config.js create mode 100644 test/sourcemaps/samples/decoded-sourcemap/input.svelte create mode 100644 test/sourcemaps/samples/decoded-sourcemap/test.js create mode 100644 test/sourcemaps/samples/preprocessed-markup/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-markup/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-markup/test.js create mode 100644 test/sourcemaps/samples/preprocessed-multiple/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-multiple/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-multiple/test.js create mode 100644 test/sourcemaps/samples/preprocessed-script/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-script/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-script/test.js create mode 100644 test/sourcemaps/samples/preprocessed-styles/_config.js create mode 100644 test/sourcemaps/samples/preprocessed-styles/input.svelte create mode 100644 test/sourcemaps/samples/preprocessed-styles/test.js create mode 100644 test/sourcemaps/samples/sourcemap-names/_config.js create mode 100644 test/sourcemaps/samples/sourcemap-names/input.svelte create mode 100644 test/sourcemaps/samples/sourcemap-names/test.js create mode 100644 test/sourcemaps/samples/sourcemap-sources/_config.js create mode 100644 test/sourcemaps/samples/sourcemap-sources/input.svelte create mode 100644 test/sourcemaps/samples/sourcemap-sources/test.js diff --git a/package-lock.json b/package-lock.json index 95167488ab..61afea32b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,16 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@ampproject/remapping": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-0.3.0.tgz", + "integrity": "sha512-dqmASpaTCavldZqwdEpokgG4yOXmEiEGPP3ATTsBbdXXSKf6kx8jt2fPcKhodABdZlYe82OehR2oFK1y9gwZxw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "1.0.0", + "sourcemap-codec": "1.4.8" + } + }, "@babel/code-frame": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", @@ -36,6 +46,12 @@ "integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==", "dev": true }, + "@jridgewell/resolve-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-1.0.0.tgz", + "integrity": "sha512-9oLAnygRMi8Q5QkYEU4XWK04B+nuoXoxjRvRxgjuChkLZFBja0YPSgdZ7dZtwhncLBcQe/I/E+fLuk5qxcYVJA==", + "dev": true + }, "@rollup/plugin-commonjs": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.0.tgz", @@ -3737,9 +3753,9 @@ } }, "sourcemap-codec": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", - "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, "spdx-correct": { diff --git a/package.json b/package.json index 61c213494c..1103d56abb 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ }, "homepage": "https://github.com/sveltejs/svelte#README", "devDependencies": { + "@ampproject/remapping": "^0.3.0", "@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-json": "^4.0.1", "@rollup/plugin-node-resolve": "^6.0.0", @@ -127,6 +128,7 @@ "rollup": "^1.27.14", "source-map": "^0.7.3", "source-map-support": "^0.5.13", + "sourcemap-codec": "^1.4.8", "tiny-glob": "^0.2.6", "tslib": "^1.10.0", "typescript": "^3.5.3" diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 6e7e41f385..b2c8820351 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -29,7 +29,9 @@ import add_to_set from './utils/add_to_set'; import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x, b } from 'code-red'; import { is_reserved_keyword } from './utils/reserved_keywords'; +import { apply_preprocessor_sourcemap } from '../utils/string_with_sourcemap'; import Element from './nodes/Element'; +import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types'; interface ComponentOptions { namespace?: string; @@ -326,6 +328,8 @@ export default class Component { js.map.sourcesContent = [ this.source ]; + + js.map = apply_preprocessor_sourcemap(this.file, js.map, compile_options.sourcemap as (string | RawSourceMap | DecodedSourceMap)); } return { diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 1faa33ee1e..842539fcde 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -11,6 +11,7 @@ const valid_options = [ 'format', 'name', 'filename', + 'sourcemap', 'generate', 'outputFilename', 'cssOutputFilename', diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 2f86202a34..fe1ee368fc 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -7,6 +7,8 @@ import { extract_names, Scope } from '../utils/scope'; import { invalidate } from './invalidate'; import Block from './Block'; import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; +import { apply_preprocessor_sourcemap } from '../../utils/string_with_sourcemap'; +import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types'; export default function dom( component: Component, @@ -30,6 +32,9 @@ export default function dom( } const css = component.stylesheet.render(options.filename, !options.customElement); + + css.map = apply_preprocessor_sourcemap(options.filename, css.map, options.sourcemap as string | RawSourceMap | DecodedSourceMap); + const styles = component.stylesheet.has_styles && options.dev ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` : css.code; diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 5249c2fd48..689b59529d 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -110,6 +110,7 @@ export interface CompileOptions { filename?: string; generate?: 'dom' | 'ssr' | false; + sourcemap?: object | string; outputFilename?: string; cssOutputFilename?: string; sveltePath?: string; diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts index 1d7d74ceac..1de41cf9bf 100644 --- a/src/compiler/preprocess/index.ts +++ b/src/compiler/preprocess/index.ts @@ -1,6 +1,11 @@ +import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types/types'; +import { decode as decode_mappings } from 'sourcemap-codec'; +import { getLocator } from 'locate-character'; +import { StringWithSourcemap, sourcemap_add_offset, combine_sourcemaps } from '../utils/string_with_sourcemap'; + export interface Processed { code: string; - map?: object | string; + map?: string | object; // we are opaque with the type here to avoid dependency on the remapping module for our public types. dependencies?: string[]; } @@ -37,12 +42,18 @@ function parse_attributes(str: string) { interface Replacement { offset: number; length: number; - replacement: string; + replacement: StringWithSourcemap; } -async function replace_async(str: string, re: RegExp, func: (...any) => Promise) { +async function replace_async( + filename: string, + source: string, + get_location: ReturnType, + re: RegExp, + func: (...any) => Promise +): Promise { const replacements: Array> = []; - str.replace(re, (...args) => { + source.replace(re, (...args) => { replacements.push( func(...args).then( res => @@ -55,16 +66,55 @@ async function replace_async(str: string, re: RegExp, func: (...any) => Promise< ); return ''; }); - let out = ''; + const out = new StringWithSourcemap(); let last_end = 0; for (const { offset, length, replacement } of await Promise.all( replacements )) { - out += str.slice(last_end, offset) + replacement; + // content = unchanged source characters before the replaced segment + const content = StringWithSourcemap.from_source( + filename, source.slice(last_end, offset), get_location(last_end)); + out.concat(content).concat(replacement); last_end = offset + length; } - out += str.slice(last_end); - return out; + // final_content = unchanged source characters after last replaced segment + const final_content = StringWithSourcemap.from_source( + filename, source.slice(last_end), get_location(last_end)); + return out.concat(final_content); +} + +/** + * Convert a preprocessor output and its leading prefix and trailing suffix into StringWithSourceMap + */ +function get_replacement( + filename: string, + offset: number, + get_location: ReturnType, + original: string, + processed: Processed, + prefix: string, + suffix: string +): StringWithSourcemap { + + // Convert the unchanged prefix and suffix to StringWithSourcemap + const prefix_with_map = StringWithSourcemap.from_source( + filename, prefix, get_location(offset)); + const suffix_with_map = StringWithSourcemap.from_source( + filename, suffix, get_location(offset + prefix.length + original.length)); + + // Convert the preprocessed code and its sourcemap to a StringWithSourcemap + let decoded_map: DecodedSourceMap; + if (processed.map) { + decoded_map = typeof processed.map === 'string' ? JSON.parse(processed.map) : processed.map; + if (typeof(decoded_map.mappings) === 'string') { + decoded_map.mappings = decode_mappings(decoded_map.mappings); + } + sourcemap_add_offset(decoded_map, get_location(offset + prefix.length)); + } + const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map); + + // Surround the processed code with the prefix and suffix, retaining valid sourcemappings + return prefix_with_map.concat(processed_with_map).concat(suffix_with_map); } export default async function preprocess( @@ -76,60 +126,92 @@ export default async function preprocess( const filename = (options && options.filename) || preprocessor.filename; // legacy const dependencies = []; - const preprocessors = Array.isArray(preprocessor) ? preprocessor : [preprocessor]; + const preprocessors = preprocessor + ? Array.isArray(preprocessor) ? preprocessor : [preprocessor] + : []; const markup = preprocessors.map(p => p.markup).filter(Boolean); const script = preprocessors.map(p => p.script).filter(Boolean); const style = preprocessors.map(p => p.style).filter(Boolean); + // sourcemap_list is sorted in reverse order from last map (index 0) to first map (index -1) + // so we use sourcemap_list.unshift() to add new maps + // https://github.com/ampproject/remapping#multiple-transformations-of-a-file + const sourcemap_list: Array = []; + + // TODO keep track: what preprocessor generated what sourcemap? to make debugging easier = detect low-resolution sourcemaps in fn combine_mappings + for (const fn of markup) { + + // run markup preprocessor const processed = await fn({ content: source, filename }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - source = processed ? processed.code : source; + + if (!processed) continue; + + if (processed.dependencies) dependencies.push(...processed.dependencies); + source = processed.code; + if (processed.map) { + sourcemap_list.unshift( + typeof(processed.map) === 'string' + ? JSON.parse(processed.map) + : processed.map + ); + } } - for (const fn of script) { - source = await replace_async( + async function preprocess_tag_content(tag_name: 'style' | 'script', preprocessor: Preprocessor) { + const get_location = getLocator(source); + const tag_regex = tag_name == 'style' + ? /|([^]*?)<\/style>|\/>)/gi + : /|([^]*?)<\/script>|\/>)/gi; + + const res = await replace_async( + filename, source, - /|([^]*?)<\/script>|\/>)/gi, - async (match, attributes = '', content = '') => { + get_location, + tag_regex, + async (match, attributes = '', content = '', offset) => { + const no_change = () => StringWithSourcemap.from_source( + filename, match, get_location(offset)); if (!attributes && !content) { - return match; + return no_change(); } attributes = attributes || ''; - const processed = await fn({ + content = content || ''; + + // run script preprocessor + const processed = await preprocessor({ content, attributes: parse_attributes(attributes), filename }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - return processed ? `${processed.code}` : match; + + if (!processed) return no_change(); + if (processed.dependencies) dependencies.push(...processed.dependencies); + return get_replacement(filename, offset, get_location, content, processed, `<${tag_name}${attributes}>`, ``); } ); + source = res.string; + sourcemap_list.unshift(res.map); + } + + for (const fn of script) { + await preprocess_tag_content('script', fn); } for (const fn of style) { - source = await replace_async( - source, - /|([^]*?)<\/style>|\/>)/gi, - async (match, attributes = '', content = '') => { - if (!attributes && !content) { - return match; - } - const processed: Processed = await fn({ - content, - attributes: parse_attributes(attributes), - filename - }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - return processed ? `${processed.code}` : match; - } - ); + await preprocess_tag_content('style', fn); } + // Combine all the source maps for each preprocessor function into one + const map: RawSourceMap = combine_sourcemaps( + filename, + sourcemap_list + ); + return { // TODO return separated output, in future version where svelte.compile supports it: // style: { code: styleCode, map: styleMap }, @@ -138,7 +220,7 @@ export default async function preprocess( code: source, dependencies: [...new Set(dependencies)], - + map: (map as object), toString() { return source; } diff --git a/src/compiler/utils/string_with_sourcemap.ts b/src/compiler/utils/string_with_sourcemap.ts new file mode 100644 index 0000000000..7f8a0ec1eb --- /dev/null +++ b/src/compiler/utils/string_with_sourcemap.ts @@ -0,0 +1,275 @@ +import { DecodedSourceMap, RawSourceMap, SourceMapLoader } from '@ampproject/remapping/dist/types/types'; +import remapping from '@ampproject/remapping'; +import { SourceMap } from 'magic-string'; + +type SourceLocation = { + line: number; + column: number; +}; + +function last_line_length(s: string) { + return s.length - s.lastIndexOf('\n') - 1; +} + +// mutate map in-place +export function sourcemap_add_offset( + map: DecodedSourceMap, offset: SourceLocation +) { + if (map.mappings.length == 0) return map; + // shift columns in first line + const segment_list = map.mappings[0]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[3]) seg[3] += offset.column; + } + // shift lines + for (let line = 0; line < map.mappings.length; line++) { + const segment_list = map.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[2]) seg[2] += offset.line; + } + } +} + +function merge_tables(this_table: T[], other_table: T[]): [T[], number[], boolean, boolean] { + const new_table = this_table.slice(); + const idx_map = []; + other_table = other_table || []; + let val_changed = false; + for (const [other_idx, other_val] of other_table.entries()) { + const this_idx = this_table.indexOf(other_val); + if (this_idx >= 0) { + idx_map[other_idx] = this_idx; + } else { + const new_idx = new_table.length; + new_table[new_idx] = other_val; + idx_map[other_idx] = new_idx; + val_changed = true; + } + } + let idx_changed = val_changed; + if (val_changed) { + if (idx_map.find((val, idx) => val != idx) === undefined) { + // idx_map is identity map [0, 1, 2, 3, 4, ....] + idx_changed = false; + } + } + return [new_table, idx_map, val_changed, idx_changed]; +} + +function pushArray(_this: T[], other: T[]) { + // We use push to mutate in place for memory and perf reasons + // We use the for loop instead of _this.push(...other) to avoid the JS engine's function argument limit (65,535 in JavascriptCore) + for (let i = 0; i < other.length; i++) { + _this.push(other[i]); + } +} + +export class StringWithSourcemap { + string: string; + map: DecodedSourceMap; + + constructor(string = '', map: DecodedSourceMap = null) { + this.string = string; + if (map) { + this.map = map as DecodedSourceMap; + } else { + this.map = { + version: 3, + mappings: [], + sources: [], + names: [] + }; + } + } + + /** + * concat in-place (mutable), return this (chainable) + * will also mutate the `other` object + */ + concat(other: StringWithSourcemap): StringWithSourcemap { + // noop: if one is empty, return the other + if (other.string == '') return this; + if (this.string == '') { + this.string = other.string; + this.map = other.map; + return this; + } + + this.string += other.string; + + const m1 = this.map; + const m2 = other.map; + + if (m2.mappings.length == 0) return this; + + // combine sources and names + const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources); + const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names); + + if (sources_changed) m1.sources = sources; + if (names_changed) m1.names = names; + + // unswitched loops are faster + if (sources_idx_changed && names_idx_changed) { + for (let line = 0; line < m2.mappings.length; line++) { + const segment_list = m2.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[1]) seg[1] = new_source_idx[seg[1]]; + if (seg[4]) seg[4] = new_name_idx[seg[4]]; + } + } + } else if (sources_idx_changed) { + for (let line = 0; line < m2.mappings.length; line++) { + const segment_list = m2.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[1]) seg[1] = new_source_idx[seg[1]]; + } + } + } else if (names_idx_changed) { + for (let line = 0; line < m2.mappings.length; line++) { + const segment_list = m2.mappings[line]; + for (let segment = 0; segment < segment_list.length; segment++) { + const seg = segment_list[segment]; + if (seg[4]) seg[4] = new_name_idx[seg[4]]; + } + } + } + + // combine the mappings + + // combine + // 1. last line of first map + // 2. first line of second map + // columns of 2 must be shifted + + const column_offset = last_line_length(this.string); + if (m2.mappings.length > 0 && column_offset > 0) { + const first_line = m2.mappings[0]; + for (let i = 0; i < first_line.length; i++) { + first_line[i][0] += column_offset; + } + } + + // combine last line + first line + pushArray(m1.mappings[m1.mappings.length - 1], m2.mappings.shift()); + + // append other lines + pushArray(m1.mappings, m2.mappings); + + return this; + } + + static from_processed(string: string, map?: DecodedSourceMap): StringWithSourcemap { + if (map) return new StringWithSourcemap(string, map); + if (string == '') return new StringWithSourcemap(); + map = { version: 3, names: [], sources: [], mappings: [] }; + + // add empty SourceMapSegment[] for every line + const line_count = (string.match(/\n/g) || '').length; + for (let i = 0; i < line_count; i++) map.mappings.push([]); + return new StringWithSourcemap(string, map); + } + + static from_source( + source_file: string, source: string, offset?: SourceLocation + ): StringWithSourcemap { + if (!offset) offset = { line: 0, column: 0 }; + const map: DecodedSourceMap = { version: 3, names: [], sources: [source_file], mappings: [] }; + if (source == '') return new StringWithSourcemap(source, map); + + // we create a high resolution identity map here, + // we know that it will eventually be merged with svelte's map, + // at which stage the resolution will decrease. + const line_list = source.split('\n'); + for (let line = 0; line < line_list.length; line++) { + map.mappings.push([]); + const token_list = line_list[line].split(/([^\d\w\s]|\s+)/g); + for (let token = 0, column = 0; token < token_list.length; token++) { + if (token_list[token] == '') continue; + map.mappings[line].push([column, 0, offset.line + line, column]); + column += token_list[token].length; + } + } + // shift columns in first line + const segment_list = map.mappings[0]; + for (let segment = 0; segment < segment_list.length; segment++) { + segment_list[segment][3] += offset.column; + } + return new StringWithSourcemap(source, map); + } +} + +export function combine_sourcemaps( + filename: string, + sourcemap_list: Array +): RawSourceMap { + if (sourcemap_list.length == 0) return null; + + let map_idx = 1; + const map: RawSourceMap = + sourcemap_list.slice(0, -1) + .find(m => m.sources.length !== 1) === undefined + + ? remapping( // use array interface + // only the oldest sourcemap can have multiple sources + sourcemap_list, + () => null, + true // skip optional field `sourcesContent` + ) + + : remapping( // use loader interface + sourcemap_list[0], // last map + function loader(sourcefile) { + if (sourcefile === filename && sourcemap_list[map_idx]) { + return sourcemap_list[map_idx++]; // idx 1, 2, ... + // bundle file = branch node + } + else return null; // source file = leaf node + } as SourceMapLoader, + true + ); + + if (!map.file) delete map.file; // skip optional field `file` + + return map; +} + +// browser vs node.js +const b64enc = typeof btoa == 'function' ? btoa : b => Buffer.from(b).toString('base64'); + +export function apply_preprocessor_sourcemap(filename: string, svelte_map: SourceMap, preprocessor_map_input: string | DecodedSourceMap | RawSourceMap): SourceMap { + if (!svelte_map || !preprocessor_map_input) return svelte_map; + + const preprocessor_map = typeof preprocessor_map_input === 'string' ? JSON.parse(preprocessor_map_input) : preprocessor_map_input; + + const result_map = combine_sourcemaps( + filename, + [ + svelte_map as RawSourceMap, + preprocessor_map + ] + ) as RawSourceMap; + + // Svelte expects a SourceMap which includes toUrl and toString. Instead of wrapping our output in a class, + // we just tack on the extra properties. + Object.defineProperties(result_map, { + toString: { + enumerable: false, + value: function toString() { + return JSON.stringify(this); + } + }, + toUrl: { + enumerable: false, + value: function toUrl() { + return 'data:application/json;charset=utf-8;base64,' + b64enc(this.toString()); + } + } + }); + + return result_map as SourceMap; +} diff --git a/test/preprocess/index.ts b/test/preprocess/index.ts index 60d3acbabb..be898bbbfd 100644 --- a/test/preprocess/index.ts +++ b/test/preprocess/index.ts @@ -24,6 +24,9 @@ describe('preprocess', () => { config.options || { filename: 'input.svelte' } ); fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code); + if (result.map) { + fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html.map`, JSON.stringify(result.map, null, 2)); + } assert.equal(result.code, expected); diff --git a/test/setup.js b/test/setup.js index 7406a07dd9..74250c10eb 100644 --- a/test/setup.js +++ b/test/setup.js @@ -12,7 +12,7 @@ require.extensions['.js'] = function(module, filename) { .replace(/^import (\w+) from ['"]([^'"]+)['"];?/gm, 'var {default: $1} = require("$2");') .replace(/^import {([^}]+)} from ['"](.+)['"];?/gm, 'var {$1} = require("$2");') .replace(/^export default /gm, 'exports.default = ') - .replace(/^export (const|let|var|class|function) (\w+)/gm, (match, type, name) => { + .replace(/^export (const|let|var|class|function|async\s+function) (\w+)/gm, (match, type, name) => { exports.push(name); return `${type} ${name}`; }) diff --git a/test/sourcemaps/helpers.ts b/test/sourcemaps/helpers.ts new file mode 100644 index 0000000000..d0bea310e6 --- /dev/null +++ b/test/sourcemaps/helpers.ts @@ -0,0 +1,20 @@ +import MagicString from 'magic-string'; + +export function magic_string_preprocessor_result(filename: string, src: MagicString) { + return { + code: src.toString(), + map: src.generateMap({ + source: filename, + hires: true, + includeContent: false + }) + }; +} + +export function magic_string_replace_all(src: MagicString, search: string, replace: string) { + let idx = src.original.indexOf(search); + if (idx == -1) throw new Error('search not found in src'); + do { + src.overwrite(idx, idx + search.length, replace, { storeName: true }); + } while ((idx = src.original.indexOf(search, idx + 1)) != -1); +} diff --git a/test/sourcemaps/index.ts b/test/sourcemaps/index.ts index 7659948744..4122c3a419 100644 --- a/test/sourcemaps/index.ts +++ b/test/sourcemaps/index.ts @@ -37,7 +37,7 @@ describe('sourcemaps', () => { const preprocessed = await svelte.preprocess( input.code, config.preprocess || {}, - { + config.options || { filename: 'input.svelte' } ); @@ -46,8 +46,10 @@ describe('sourcemaps', () => { preprocessed.code, { filename: 'input.svelte', // filenames for sourcemaps + sourcemap: preprocessed.map, outputFilename: `${outputName}.js`, - cssOutputFilename: `${outputName}.css` + cssOutputFilename: `${outputName}.css`, + ...(config.compile_options || {}) }); js.code = js.code.replace( @@ -107,7 +109,7 @@ describe('sourcemaps', () => { css.mapConsumer = css.map && await new SourceMapConsumer(css.map); css.locate = getLocator(css.code || ''); css.locate_1 = getLocator(css.code || '', { offsetLine: 1 }); - test({ assert, input, preprocessed, js, css }); + await test({ assert, input, preprocessed, js, css }); }); }); }); diff --git a/test/sourcemaps/samples/compile-option-dev/_config.js b/test/sourcemaps/samples/compile-option-dev/_config.js new file mode 100644 index 0000000000..b6ea851b6d --- /dev/null +++ b/test/sourcemaps/samples/compile-option-dev/_config.js @@ -0,0 +1,21 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + compile_options: { + dev: true + }, + preprocess: [ + { style: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, '--replace-me-once', '\n --done-replace-once'); + magic_string_replace_all(src, '--replace-me-twice', '\n--almost-done-replace-twice'); + return magic_string_preprocessor_result(filename, src); + } }, + { style: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, '--almost-done-replace-twice', '\n --done-replace-twice'); + return magic_string_preprocessor_result(filename, src); + } } + ] +}; diff --git a/test/sourcemaps/samples/compile-option-dev/input.svelte b/test/sourcemaps/samples/compile-option-dev/input.svelte new file mode 100644 index 0000000000..6d5f91158d --- /dev/null +++ b/test/sourcemaps/samples/compile-option-dev/input.svelte @@ -0,0 +1,15 @@ +

Testing Styles

+

Testing Styles 2

+
Testing Styles 3
+ + diff --git a/test/sourcemaps/samples/compile-option-dev/test.js b/test/sourcemaps/samples/compile-option-dev/test.js new file mode 100644 index 0000000000..bf240a5a89 --- /dev/null +++ b/test/sourcemaps/samples/compile-option-dev/test.js @@ -0,0 +1,40 @@ +import { SourceMapConsumer } from 'source-map'; + +const b64dec = s => Buffer.from(s, 'base64').toString(); + +export async function test({ assert, css, js }) { + + // We check that the css source map embedded in the js is accurate + const match = js.code.match(/\tstyle\.textContent = "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?";\n/); + assert.notEqual(match, null); + + const [mimeType, encoding, cssMapBase64] = match.slice(2); + assert.equal(mimeType, 'application/json'); + assert.equal(encoding, 'utf-8'); + + const cssMapJson = b64dec(cssMapBase64); + css.mapConsumer = await new SourceMapConsumer(cssMapJson); + + // TODO make util fn + move to test index.js + const sourcefile = 'input.svelte'; + [ + // TODO how to get line + column numbers? + [css, '--keep-me', 13, 2], + [css, '--done-replace-once', 6, 5], + [css, '--done-replace-twice', 9, 5] + ] + .forEach(([where, content, line, column]) => { + assert.deepEqual( + where.mapConsumer.originalPositionFor( + where.locate_1(content) + ), + { + source: sourcefile, + name: null, + line, + column + }, + `failed to locate "${content}" from "${sourcefile}"` + ); + }); +} diff --git a/test/sourcemaps/samples/decoded-sourcemap/_config.js b/test/sourcemaps/samples/decoded-sourcemap/_config.js new file mode 100644 index 0000000000..dd4eee9461 --- /dev/null +++ b/test/sourcemaps/samples/decoded-sourcemap/_config.js @@ -0,0 +1,15 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + + js_map_sources: [], // test component has no scripts + + preprocess: { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'replace me', 'success'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/decoded-sourcemap/input.svelte b/test/sourcemaps/samples/decoded-sourcemap/input.svelte new file mode 100644 index 0000000000..b233d7f670 --- /dev/null +++ b/test/sourcemaps/samples/decoded-sourcemap/input.svelte @@ -0,0 +1,2 @@ +

decoded-sourcemap

+
replace me
diff --git a/test/sourcemaps/samples/decoded-sourcemap/test.js b/test/sourcemaps/samples/decoded-sourcemap/test.js new file mode 100644 index 0000000000..5a44ee83cf --- /dev/null +++ b/test/sourcemaps/samples/decoded-sourcemap/test.js @@ -0,0 +1,19 @@ +export function test({ assert, input, preprocessed }) { + + const expected = input.locate('replace me'); + + const start = preprocessed.locate('success'); + + const actualbar = preprocessed.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'replace me', + line: expected.line + 1, + column: expected.column + }); + +} diff --git a/test/sourcemaps/samples/preprocessed-markup/_config.js b/test/sourcemaps/samples/preprocessed-markup/_config.js new file mode 100644 index 0000000000..0b2baeb49c --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-markup/_config.js @@ -0,0 +1,12 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-markup/input.svelte b/test/sourcemaps/samples/preprocessed-markup/input.svelte new file mode 100644 index 0000000000..ee4b90372a --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-markup/input.svelte @@ -0,0 +1,5 @@ + + +{foo.baritone.baz} diff --git a/test/sourcemaps/samples/preprocessed-markup/test.js b/test/sourcemaps/samples/preprocessed-markup/test.js new file mode 100644 index 0000000000..b587103969 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-markup/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, js }) { + const expectedBar = input.locate('baritone.baz'); + const expectedBaz = input.locate('.baz'); + + let start = js.locate('bar.baz'); + + const actualbar = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'baritone', + line: expectedBar.line + 1, + column: expectedBar.column + }); + + start = js.locate('.baz'); + + const actualbaz = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: null, + line: expectedBaz.line + 1, + column: expectedBaz.column + }); +} diff --git a/test/sourcemaps/samples/preprocessed-multiple/_config.js b/test/sourcemaps/samples/preprocessed-multiple/_config.js new file mode 100644 index 0000000000..39259a02a8 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-multiple/_config.js @@ -0,0 +1,25 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + magic_string_replace_all(src, '--bazitone', '--baz'); + return magic_string_preprocessor_result(filename, src); + }, + script: ({ content, filename }) => { + const src = new MagicString(content); + const idx = content.indexOf('bar'); + src.prependLeft(idx, ' '); + return magic_string_preprocessor_result(filename, src); + }, + style: ({ content, filename }) => { + const src = new MagicString(content); + const idx = content.indexOf('--baz'); + src.prependLeft(idx, ' '); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-multiple/input.svelte b/test/sourcemaps/samples/preprocessed-multiple/input.svelte new file mode 100644 index 0000000000..e656d399ae --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-multiple/input.svelte @@ -0,0 +1,9 @@ + + +

multiple {foo}

diff --git a/test/sourcemaps/samples/preprocessed-multiple/test.js b/test/sourcemaps/samples/preprocessed-multiple/test.js new file mode 100644 index 0000000000..a0cfe1fa8a --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-multiple/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, js, css }) { + const expectedBar = input.locate('baritone'); + const expectedBaz = input.locate('--bazitone'); + + let start = js.locate('bar'); + + const actualbar = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'baritone', + line: expectedBar.line + 1, + column: expectedBar.column + }); + + start = css.locate('--baz'); + + const actualbaz = css.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: '--bazitone', + line: expectedBaz.line + 1, + column: expectedBaz.column + }); +} diff --git a/test/sourcemaps/samples/preprocessed-script/_config.js b/test/sourcemaps/samples/preprocessed-script/_config.js new file mode 100644 index 0000000000..94bd83c22f --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-script/_config.js @@ -0,0 +1,12 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + script: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-script/input.svelte b/test/sourcemaps/samples/preprocessed-script/input.svelte new file mode 100644 index 0000000000..11586619e1 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-script/input.svelte @@ -0,0 +1,9 @@ + + +

{foo.bar.baz}

diff --git a/test/sourcemaps/samples/preprocessed-script/test.js b/test/sourcemaps/samples/preprocessed-script/test.js new file mode 100644 index 0000000000..20a366f6d2 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-script/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, js }) { + const expectedBar = input.locate('baritone:'); + const expectedBaz = input.locate('baz:'); + + let start = js.locate('bar:'); + + const actualbar = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: 'baritone', + line: expectedBar.line + 1, + column: expectedBar.column + }, "couldn't find bar: in source"); + + start = js.locate('baz:'); + + const actualbaz = js.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: null, + line: expectedBaz.line + 1, + column: expectedBaz.column + }, "couldn't find baz: in source"); +} diff --git a/test/sourcemaps/samples/preprocessed-styles/_config.js b/test/sourcemaps/samples/preprocessed-styles/_config.js new file mode 100644 index 0000000000..04c8bcda7a --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-styles/_config.js @@ -0,0 +1,12 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: { + style: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + return magic_string_preprocessor_result(filename, src); + } + } +}; diff --git a/test/sourcemaps/samples/preprocessed-styles/input.svelte b/test/sourcemaps/samples/preprocessed-styles/input.svelte new file mode 100644 index 0000000000..0d942390f4 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-styles/input.svelte @@ -0,0 +1,12 @@ +

Testing Styles

+

Testing Styles 2

+ + diff --git a/test/sourcemaps/samples/preprocessed-styles/test.js b/test/sourcemaps/samples/preprocessed-styles/test.js new file mode 100644 index 0000000000..5b28a12514 --- /dev/null +++ b/test/sourcemaps/samples/preprocessed-styles/test.js @@ -0,0 +1,32 @@ +export function test({ assert, input, css }) { + const expectedBar = input.locate('--baritone'); + const expectedBaz = input.locate('--baz'); + + let start = css.locate('--bar'); + + const actualbar = css.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbar, { + source: 'input.svelte', + name: null, + line: expectedBar.line + 1, + column: expectedBar.column + }, "couldn't find bar in source"); + + start = css.locate('--baz'); + + const actualbaz = css.mapConsumer.originalPositionFor({ + line: start.line + 1, + column: start.column + }); + + assert.deepEqual(actualbaz, { + source: 'input.svelte', + name: null, + line: expectedBaz.line + 1, + column: expectedBaz.column + }, "couldn't find baz in source"); +} diff --git a/test/sourcemaps/samples/sourcemap-names/_config.js b/test/sourcemaps/samples/sourcemap-names/_config.js new file mode 100644 index 0000000000..c8557ba465 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-names/_config.js @@ -0,0 +1,33 @@ +import MagicString from 'magic-string'; +import { magic_string_preprocessor_result, magic_string_replace_all } from '../../helpers'; + +export default { + preprocess: [ + { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'baritone', 'bar'); + magic_string_replace_all(src,'--bazitone', '--baz'); + magic_string_replace_all(src,'old_name_1', 'temp_new_name_1'); + magic_string_replace_all(src,'old_name_2', 'temp_new_name_2'); + return magic_string_preprocessor_result(filename, src); + } + }, + { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'temp_new_name_1', 'temp_temp_new_name_1'); + magic_string_replace_all(src, 'temp_new_name_2', 'temp_temp_new_name_2'); + return magic_string_preprocessor_result(filename, src); + } + }, + { + markup: ({ content, filename }) => { + const src = new MagicString(content); + magic_string_replace_all(src, 'temp_temp_new_name_1', 'new_name_1'); + magic_string_replace_all(src, 'temp_temp_new_name_2', 'new_name_2'); + return magic_string_preprocessor_result(filename, src); + } + } + ] +}; diff --git a/test/sourcemaps/samples/sourcemap-names/input.svelte b/test/sourcemaps/samples/sourcemap-names/input.svelte new file mode 100644 index 0000000000..b62715a857 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-names/input.svelte @@ -0,0 +1,12 @@ + + +

use-names

+
{old_name_1.baritone}
+
{old_name_2}
diff --git a/test/sourcemaps/samples/sourcemap-names/test.js b/test/sourcemaps/samples/sourcemap-names/test.js new file mode 100644 index 0000000000..cd70bd25ce --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-names/test.js @@ -0,0 +1,42 @@ +// needed for workaround, TODO remove +import { getLocator } from 'locate-character'; + +export function test({ assert, preprocessed, js, css }) { + + assert.deepEqual( + preprocessed.map.names.sort(), + ['baritone', '--bazitone', 'old_name_1', 'old_name_2'].sort() + ); + + function test_name(old_name, new_name, where) { + + let loc = { character: -1 }; + while (loc = where.locate(new_name, loc.character + 1)) { + const actualMapping = where.mapConsumer.originalPositionFor({ + line: loc.line + 1, column: loc.column + }); + if (actualMapping.line === null) { + // location is not mapped - ignore + continue; + } + assert.equal(actualMapping.name, old_name); + } + if (loc === undefined) { + // workaround for bug in locate-character, TODO remove + // https://github.com/Rich-Harris/locate-character/pull/5 + where.locate = getLocator(where.code); + } + } + + test_name('baritone', 'bar', js); + test_name('baritone', 'bar', preprocessed); + + test_name('--bazitone', '--baz', css); + test_name('--bazitone', '--baz', preprocessed); + + test_name('old_name_1', 'new_name_1', js); + test_name('old_name_1', 'new_name_1', preprocessed); + + test_name('old_name_2', 'new_name_2', js); + test_name('old_name_2', 'new_name_2', preprocessed); +} diff --git a/test/sourcemaps/samples/sourcemap-sources/_config.js b/test/sourcemaps/samples/sourcemap-sources/_config.js new file mode 100644 index 0000000000..97024cea5f --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-sources/_config.js @@ -0,0 +1,60 @@ +/* eslint-disable import/no-duplicates */ +/* the code that transforms these to commonjs, can't handle "MagicString, { Bundle } from.." */ + +import MagicString from 'magic-string'; +import { Bundle } from 'magic-string'; + + +function add(bundle, filename, source) { + bundle.addSource({ + filename, + content: new MagicString(source), + separator: '\n' + //separator: '' // ERROR. probably a bug in magic-string + }); +} + +function result(bundle, filename) { + return { + code: bundle.toString(), + map: bundle.generateMap({ + file: filename, + includeContent: false, + hires: true // required for remapping + }) + }; +} + +export default { + js_map_sources: [ + 'input.svelte', + 'foo.js', + 'bar.js', + 'foo2.js', + 'bar2.js' + ], + preprocess: [ + { + script: ({ content, filename }) => { + const bundle = new Bundle(); + + add(bundle, filename, content); + add(bundle, 'foo.js', 'var answer = 42; // foo.js\n'); + add(bundle, 'bar.js', 'console.log(answer); // bar.js\n'); + + return result(bundle, filename); + } + }, + { + script: ({ content, filename }) => { + const bundle = new Bundle(); + + add(bundle, filename, content); + add(bundle, 'foo2.js', 'var answer2 = 84; // foo2.js\n'); + add(bundle, 'bar2.js', 'console.log(answer2); // bar2.js\n'); + + return result(bundle, filename); + } + } + ] +}; diff --git a/test/sourcemaps/samples/sourcemap-sources/input.svelte b/test/sourcemaps/samples/sourcemap-sources/input.svelte new file mode 100644 index 0000000000..33c8a9d9a6 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-sources/input.svelte @@ -0,0 +1,4 @@ + +

sourcemap-sources

diff --git a/test/sourcemaps/samples/sourcemap-sources/test.js b/test/sourcemaps/samples/sourcemap-sources/test.js new file mode 100644 index 0000000000..78a4c80a17 --- /dev/null +++ b/test/sourcemaps/samples/sourcemap-sources/test.js @@ -0,0 +1,29 @@ +export function test({ assert, preprocessed, js }) { + + assert.equal(preprocessed.error, undefined); + + // sourcemap stores location only for 'answer = 42;' + // not for 'var answer = 42;' + [ + [js, 'foo.js', 'answer = 42;', 4], + [js, 'bar.js', 'console.log(answer);', 0], + [js, 'foo2.js', 'answer2 = 84;', 4], + [js, 'bar2.js', 'console.log(answer2);', 0] + ] + .forEach(([where, sourcefile, content, column]) => { + + assert.deepEqual( + where.mapConsumer.originalPositionFor( + where.locate_1(content) + ), + { + source: sourcefile, + name: null, + line: 1, + column + }, + `failed to locate "${content}" from "${sourcefile}"` + ); + + }); +} From 67dea941bb1e61f0912ebd2257666b899c1ccefa Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 19 Nov 2020 10:45:03 -0800 Subject: [PATCH 12/12] Fix eslint issue (#5698) --- src/compiler/utils/string_with_sourcemap.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/utils/string_with_sourcemap.ts b/src/compiler/utils/string_with_sourcemap.ts index 7f8a0ec1eb..421a0c1fbd 100644 --- a/src/compiler/utils/string_with_sourcemap.ts +++ b/src/compiler/utils/string_with_sourcemap.ts @@ -227,8 +227,9 @@ export function combine_sourcemaps( if (sourcefile === filename && sourcemap_list[map_idx]) { return sourcemap_list[map_idx++]; // idx 1, 2, ... // bundle file = branch node + } else { + return null; // source file = leaf node } - else return null; // source file = leaf node } as SourceMapLoader, true );