Merge branch 'master' into customStyleTag

pull/5639/head
Ivan Hofer 5 years ago
commit 7b56dba158

@ -37,6 +37,8 @@ If you have a stack trace to include, we recommend putting inside a `<details>`
</details> </details>
**Information about your Svelte project:** **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 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) - Your operating system: (e.x. OS X 10, Ubuntu Linux 19.10, Windows XP, etc)

@ -1,5 +1,9 @@
# Svelte changelog # Svelte changelog
## 3.29.7
* Include `./register` in exports map ([#5670](https://github.com/sveltejs/svelte/issues/5670))
## 3.29.6 ## 3.29.6
* Include `./package.json` in export map ([#5659](https://github.com/sveltejs/svelte/issues/5659)) * Include `./package.json` in export map ([#5659](https://github.com/sveltejs/svelte/issues/5659))

24
package-lock.json generated

@ -1,9 +1,19 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.29.6", "version": "3.29.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "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": { "@babel/code-frame": {
"version": "7.10.1", "version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
@ -36,6 +46,12 @@
"integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==", "integrity": "sha512-KioOCsSvSvXx6xUNLiJz+P+VMb7NRcePjoefOr74Y5P6lEKsiOn35eZyZzgpK4XCNJdXTDR7+zykj0lwxRvZ2g==",
"dev": true "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": { "@rollup/plugin-commonjs": {
"version": "11.0.0", "version": "11.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.0.0.tgz",
@ -3737,9 +3753,9 @@
} }
}, },
"sourcemap-codec": { "sourcemap-codec": {
"version": "1.4.6", "version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true "dev": true
}, },
"spdx-correct": { "spdx-correct": {

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.29.6", "version": "3.29.7",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",
@ -44,6 +44,9 @@
"import": "./motion/index.mjs", "import": "./motion/index.mjs",
"require": "./motion/index.js" "require": "./motion/index.js"
}, },
"./register": {
"require": "./register.js"
},
"./store": { "./store": {
"import": "./store/index.mjs", "import": "./store/index.mjs",
"require": "./store/index.js" "require": "./store/index.js"
@ -91,6 +94,7 @@
}, },
"homepage": "https://github.com/sveltejs/svelte#README", "homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": { "devDependencies": {
"@ampproject/remapping": "^0.3.0",
"@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1", "@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0", "@rollup/plugin-node-resolve": "^6.0.0",
@ -124,6 +128,7 @@
"rollup": "^1.27.14", "rollup": "^1.27.14",
"source-map": "^0.7.3", "source-map": "^0.7.3",
"source-map-support": "^0.5.13", "source-map-support": "^0.5.13",
"sourcemap-codec": "^1.4.8",
"tiny-glob": "^0.2.6", "tiny-glob": "^0.2.6",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"typescript": "^3.5.3" "typescript": "^3.5.3"

@ -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: Once installed, you'll have access to three new commands:
* `node my-file.js` — runs the JavaScript in `my-file.js` * `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 * `npx [subcommand]` — a convenient way to run programs available on npm without permanently installing them
@ -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). 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. 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.)
(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.)
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): 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):

@ -300,6 +300,9 @@ An each block can also have an `{:else}` clause, which is rendered if the list i
```sv ```sv
{#await expression then name}...{/await} {#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} {/await}
``` ```
---
If conversely you only want to show the error state, you can omit the `then` block.
```sv
{#await promise catch error}
<p>The error is {error}</p>
{/await}
```
### {#key ...} ### {#key ...}
```sv ```sv

@ -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. Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the only one that runs inside a server-side component.

@ -0,0 +1,13 @@
<script>
import Profile from "./Profile.svelte";
</script>
<Profile>
<span slot="name">Bob</span>
<span slot="email">bob@email.com</span>
</Profile>
<Profile>
<span slot="name">Alice</span>
<span slot="phone">12345678</span>
</Profile>

@ -0,0 +1,24 @@
<style>
section {
width: 200px;
display: grid;
grid-template-columns: 1fr 1fr;
padding: 16px;
box-shadow: 2px 2px 4px #dedede;
border: 1px solid #888;
margin-bottom: 16px;
}
</style>
<section>
<div>Name</div>
<slot name="name" />
{#if $$slots.email}
<div>Email</div>
<slot name="email" />
{/if}
{#if $$slots.phone}
<div>Phone</div>
<slot name="phone" />
{/if}
</section>

@ -5,9 +5,9 @@
onMount(() => { onMount(() => {
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
let frame; let frame = requestAnimationFrame(loop);
(function loop() { function loop(t) {
frame = requestAnimationFrame(loop); frame = requestAnimationFrame(loop);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
@ -17,8 +17,6 @@
const x = i % canvas.width; const x = i % canvas.width;
const y = i / canvas.height >>> 0; const y = i / canvas.height >>> 0;
const t = window.performance.now();
const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000)); 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 g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000));
const b = 128; const b = 128;
@ -30,7 +28,7 @@
} }
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
}()); }
return () => { return () => {
cancelAnimationFrame(frame); cancelAnimationFrame(frame);

@ -5,9 +5,9 @@
onMount(() => { onMount(() => {
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
let frame; let frame = requestAnimationFrame(loop);
(function loop() { function loop(t) {
frame = requestAnimationFrame(loop); frame = requestAnimationFrame(loop);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
@ -17,8 +17,6 @@
const x = i % canvas.width; const x = i % canvas.width;
const y = i / canvas.height >>> 0; const y = i / canvas.height >>> 0;
const t = window.performance.now();
const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000)); 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 g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000));
const b = 128; const b = 128;
@ -30,7 +28,7 @@
} }
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
}()); }
return () => { return () => {
cancelAnimationFrame(frame); cancelAnimationFrame(frame);

@ -7,12 +7,12 @@
/* colors --------------------------------- */ /* colors --------------------------------- */
pre[class*='language-'] { pre[class*='language-'] {
--background: var(--back-light); --background: var(--back-light);
--base: hsl(45, 7%, 45%); --base: #545454;
--comment: hsl(210, 25%, 60%); --comment: #696969;
--keyword: hsl(204, 58%, 45%); --keyword: #007f8a;
--function: hsl(19, 67%, 45%); --function: #bb5525;
--string: hsl(41, 37%, 45%); --string: #856e3d;
--number: hsl(102, 27%, 50%); --number: #008000;
--tags: var(--function); --tags: var(--function);
--important: var(--string); --important: var(--string);
} }

@ -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 check_graph_for_cycles from './utils/check_graph_for_cycles';
import { print, x, b } from 'code-red'; import { print, x, b } from 'code-red';
import { is_reserved_keyword } from './utils/reserved_keywords'; import { is_reserved_keyword } from './utils/reserved_keywords';
import { apply_preprocessor_sourcemap } from '../utils/string_with_sourcemap';
import Element from './nodes/Element'; import Element from './nodes/Element';
import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
@ -326,6 +328,8 @@ export default class Component {
js.map.sourcesContent = [ js.map.sourcesContent = [
this.source this.source
]; ];
js.map = apply_preprocessor_sourcemap(this.file, js.map, compile_options.sourcemap as (string | RawSourceMap | DecodedSourceMap));
} }
return { return {

@ -11,6 +11,7 @@ const valid_options = [
'format', 'format',
'name', 'name',
'filename', 'filename',
'sourcemap',
'generate', 'generate',
'outputFilename', 'outputFilename',
'cssOutputFilename', 'cssOutputFilename',

@ -7,6 +7,8 @@ import { extract_names, Scope } from '../utils/scope';
import { invalidate } from './invalidate'; import { invalidate } from './invalidate';
import Block from './Block'; import Block from './Block';
import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression, Expression } from 'estree'; 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( export default function dom(
component: Component, component: Component,
@ -30,6 +32,9 @@ export default function dom(
} }
const css = component.stylesheet.render(options.filename, !options.customElement); 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 const styles = component.stylesheet.has_styles && options.dev
? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */`
: css.code; : css.code;

@ -110,6 +110,7 @@ export interface CompileOptions {
filename?: string; filename?: string;
generate?: 'dom' | 'ssr' | false; generate?: 'dom' | 'ssr' | false;
sourcemap?: object | string;
outputFilename?: string; outputFilename?: string;
cssOutputFilename?: string; cssOutputFilename?: string;
sveltePath?: string; sveltePath?: string;

@ -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 { export interface Processed {
code: string; 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[]; dependencies?: string[];
} }
@ -37,12 +42,18 @@ function parse_attributes(str: string) {
interface Replacement { interface Replacement {
offset: number; offset: number;
length: number; length: number;
replacement: string; replacement: StringWithSourcemap;
} }
async function replace_async(str: string, re: RegExp, func: (...any) => Promise<string>) { async function replace_async(
filename: string,
source: string,
get_location: ReturnType<typeof getLocator>,
re: RegExp,
func: (...any) => Promise<StringWithSourcemap>
): Promise<StringWithSourcemap> {
const replacements: Array<Promise<Replacement>> = []; const replacements: Array<Promise<Replacement>> = [];
str.replace(re, (...args) => { source.replace(re, (...args) => {
replacements.push( replacements.push(
func(...args).then( func(...args).then(
res => res =>
@ -55,16 +66,55 @@ async function replace_async(str: string, re: RegExp, func: (...any) => Promise<
); );
return ''; return '';
}); });
let out = ''; const out = new StringWithSourcemap();
let last_end = 0; let last_end = 0;
for (const { offset, length, replacement } of await Promise.all( for (const { offset, length, replacement } of await Promise.all(
replacements 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; last_end = offset + length;
} }
out += str.slice(last_end); // final_content = unchanged source characters after last replaced segment
return out; 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<typeof getLocator>,
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( export default async function preprocess(
@ -76,60 +126,92 @@ export default async function preprocess(
const filename = (options && options.filename) || preprocessor.filename; // legacy const filename = (options && options.filename) || preprocessor.filename; // legacy
const dependencies = []; 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 markup = preprocessors.map(p => p.markup).filter(Boolean);
const script = preprocessors.map(p => p.script).filter(Boolean); const script = preprocessors.map(p => p.script).filter(Boolean);
const style = preprocessors.map(p => p.style).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<DecodedSourceMap | RawSourceMap> = [];
// 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) { for (const fn of markup) {
// run markup preprocessor
const processed = await fn({ const processed = await fn({
content: source, content: source,
filename 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) { async function preprocess_tag_content(tag_name: 'style' | 'script', preprocessor: Preprocessor) {
source = await replace_async( const get_location = getLocator(source);
const tag_regex = tag_name == 'style'
? /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi
: /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
const res = await replace_async(
filename,
source, source,
/<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi, get_location,
async (match, attributes = '', content = '') => { tag_regex,
async (match, attributes = '', content = '', offset) => {
const no_change = () => StringWithSourcemap.from_source(
filename, match, get_location(offset));
if (!attributes && !content) { if (!attributes && !content) {
return match; return no_change();
} }
attributes = attributes || ''; attributes = attributes || '';
const processed = await fn({ content = content || '';
// run script preprocessor
const processed = await preprocessor({
content, content,
attributes: parse_attributes(attributes), attributes: parse_attributes(attributes),
filename filename
}); });
if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
return processed ? `<script${attributes}>${processed.code}</script>` : 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}>`, `</${tag_name}>`);
} }
); );
source = res.string;
sourcemap_list.unshift(res.map);
}
for (const fn of script) {
await preprocess_tag_content('script', fn);
} }
for (const fn of style) { for (const fn of style) {
source = await replace_async( await preprocess_tag_content('style', fn);
source,
/<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/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 ? `<style${attributes}>${processed.code}</style>` : match;
}
);
} }
// Combine all the source maps for each preprocessor function into one
const map: RawSourceMap = combine_sourcemaps(
filename,
sourcemap_list
);
return { return {
// TODO return separated output, in future version where svelte.compile supports it: // TODO return separated output, in future version where svelte.compile supports it:
// style: { code: styleCode, map: styleMap }, // style: { code: styleCode, map: styleMap },
@ -138,7 +220,7 @@ export default async function preprocess(
code: source, code: source,
dependencies: [...new Set(dependencies)], dependencies: [...new Set(dependencies)],
map: (map as object),
toString() { toString() {
return source; return source;
} }

@ -0,0 +1,276 @@
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<T>(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<T>(_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<DecodedSourceMap | RawSourceMap>
): 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;
}

@ -24,6 +24,9 @@ describe('preprocess', () => {
config.options || { filename: 'input.svelte' } config.options || { filename: 'input.svelte' }
); );
fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code); 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); assert.equal(result.code, expected);

@ -12,7 +12,7 @@ require.extensions['.js'] = function(module, filename) {
.replace(/^import (\w+) from ['"]([^'"]+)['"];?/gm, 'var {default: $1} = require("$2");') .replace(/^import (\w+) from ['"]([^'"]+)['"];?/gm, 'var {default: $1} = require("$2");')
.replace(/^import {([^}]+)} from ['"](.+)['"];?/gm, 'var {$1} = require("$2");') .replace(/^import {([^}]+)} from ['"](.+)['"];?/gm, 'var {$1} = require("$2");')
.replace(/^export default /gm, 'exports.default = ') .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); exports.push(name);
return `${type} ${name}`; return `${type} ${name}`;
}) })

@ -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);
}

@ -37,7 +37,7 @@ describe('sourcemaps', () => {
const preprocessed = await svelte.preprocess( const preprocessed = await svelte.preprocess(
input.code, input.code,
config.preprocess || {}, config.preprocess || {},
{ config.options || {
filename: 'input.svelte' filename: 'input.svelte'
} }
); );
@ -46,8 +46,10 @@ describe('sourcemaps', () => {
preprocessed.code, { preprocessed.code, {
filename: 'input.svelte', filename: 'input.svelte',
// filenames for sourcemaps // filenames for sourcemaps
sourcemap: preprocessed.map,
outputFilename: `${outputName}.js`, outputFilename: `${outputName}.js`,
cssOutputFilename: `${outputName}.css` cssOutputFilename: `${outputName}.css`,
...(config.compile_options || {})
}); });
js.code = js.code.replace( js.code = js.code.replace(
@ -107,7 +109,7 @@ describe('sourcemaps', () => {
css.mapConsumer = css.map && await new SourceMapConsumer(css.map); css.mapConsumer = css.map && await new SourceMapConsumer(css.map);
css.locate = getLocator(css.code || ''); css.locate = getLocator(css.code || '');
css.locate_1 = getLocator(css.code || '', { offsetLine: 1 }); css.locate_1 = getLocator(css.code || '', { offsetLine: 1 });
test({ assert, input, preprocessed, js, css }); await test({ assert, input, preprocessed, js, css });
}); });
}); });
}); });

@ -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);
} }
]
};

@ -0,0 +1,15 @@
<h1>Testing Styles</h1>
<h2>Testing Styles 2</h2>
<div>Testing Styles 3</div>
<script>export const b = 2;</script>
<style>
h1 {
--replace-me-once: red;
}
h2 {
--replace-me-twice: green;
}
div {
--keep-me: blue;
}
</style>

@ -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}"`
);
});
}

@ -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);
}
}
};

@ -0,0 +1,2 @@
<h1>decoded-sourcemap</h1>
<div>replace me</div>

@ -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
});
}

@ -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);
}
}
};

@ -0,0 +1,5 @@
<script>
export let foo;
</script>
{foo.baritone.baz}

@ -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
});
}

@ -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);
}
}
};

@ -0,0 +1,9 @@
<script>
export let foo = { baritone: 5 }
</script>
<style>
h1 {
background-color: var(--bazitone);
}
</style>
<h1>multiple {foo}</h1>

@ -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
});
}

@ -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);
}
}
};

@ -0,0 +1,9 @@
<style>
h1 {
color: red;
}
</style>
<script>
export let foo = { baritone: { baz: 5 } }
</script>
<h1>{foo.bar.baz}</h1>

@ -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");
}

@ -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);
}
}
};

@ -0,0 +1,12 @@
<h1>Testing Styles</h1>
<h2>Testing Styles 2</h2>
<script>export const b = 2;</script>
<style>
h1 {
--baritone: red;
}
h2 {
--baz: blue;
}
</style>

@ -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");
}

@ -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);
}
}
]
};

@ -0,0 +1,12 @@
<script>
export let old_name_1 = { baritone: 5 };
let old_name_2 = 'value_2';
</script>
<style>
div {
background-color: var(--bazitone);
}
</style>
<h1>use-names</h1>
<div>{old_name_1.baritone}</div>
<pre style="color: var(--bazitone)">{old_name_2}</pre>

@ -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);
}

@ -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);
}
}
]
};

@ -0,0 +1,4 @@
<script>
export let name;
</script>
<h1>sourcemap-sources</h1>

@ -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}"`
);
});
}
Loading…
Cancel
Save