From b049c9086e8e3d10f8e4fa01013c695a5a293836 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 23 May 2023 15:46:48 +0200 Subject: [PATCH 1/5] change mapping order, attributes support --- site/content/docs/05-compile-time.md | 72 ++++++++++--------- src/compiler/preprocess/index.js | 65 +++++++++++------ src/compiler/preprocess/public.d.ts | 42 +++++++++++ .../samples/multiple-preprocessors/_config.js | 8 +-- .../multiple-preprocessors/output.svelte | 4 +- .../style-attributes-modified/_config.js | 14 ++++ .../style-attributes-modified/input.svelte | 1 + .../style-attributes-modified/output.svelte | 1 + 8 files changed, 143 insertions(+), 64 deletions(-) create mode 100644 test/preprocess/samples/style-attributes-modified/_config.js create mode 100644 test/preprocess/samples/style-attributes-modified/input.svelte create mode 100644 test/preprocess/samples/style-attributes-modified/output.svelte diff --git a/site/content/docs/05-compile-time.md b/site/content/docs/05-compile-time.md index 45dd454ea01d..2992bf0a3d88 100644 --- a/site/content/docs/05-compile-time.md +++ b/site/content/docs/05-compile-time.md @@ -186,7 +186,7 @@ const ast = svelte.parse(source, { filename: 'App.svelte' }); ### `svelte.preprocess` -A number of [community-maintained preprocessing plugins](https://sveltesociety.dev/tools#preprocessors) are available to allow you to use Svelte with tools like TypeScript, PostCSS, SCSS, and Less. +A number of [official and community-maintained preprocessing plugins](https://sveltesociety.dev/tools#preprocessors) are available to allow you to use Svelte with tools like TypeScript, PostCSS, SCSS, and Less. You can write your own preprocessor using the `svelte.preprocess` API. @@ -197,6 +197,7 @@ result: { } = await svelte.preprocess( source: string, preprocessors: Array<{ + name: string, markup?: (input: { content: string, filename: string }) => Promise<{ code: string, dependencies?: Array @@ -220,48 +221,41 @@ result: { The `preprocess` function provides convenient hooks for arbitrarily transforming component source code. For example, it can be used to convert a ` \ No newline at end of file diff --git a/test/preprocess/samples/style-attributes-modified/_config.js b/test/preprocess/samples/style-attributes-modified/_config.js new file mode 100644 index 000000000000..cfeae0c02794 --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified/_config.js @@ -0,0 +1,14 @@ +import * as assert from 'node:assert'; + +export default { + preprocess: { + style: ({ attributes }) => { + assert.deepEqual(attributes, { + lang: 'scss', + 'data-foo': 'bar', + bool: true + }); + return { code: 'PROCESSED', attributes: { sth: 'else' } }; + } + } +}; diff --git a/test/preprocess/samples/style-attributes-modified/input.svelte b/test/preprocess/samples/style-attributes-modified/input.svelte new file mode 100644 index 000000000000..2c41c249649a --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified/input.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/preprocess/samples/style-attributes-modified/output.svelte b/test/preprocess/samples/style-attributes-modified/output.svelte new file mode 100644 index 000000000000..31191f9fa0bd --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified/output.svelte @@ -0,0 +1 @@ + \ No newline at end of file From 61e29d82792c09254c5577d3fdac2f7c8b36e99e Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 23 May 2023 16:38:43 +0200 Subject: [PATCH 2/5] fix source mapping when modifying attributes, add source map test capability --- site/content/docs/05-compile-time.md | 2 +- src/compiler/preprocess/index.js | 74 +++++++++++++++++-- test/preprocess/preprocess.test.js | 8 ++ test/preprocess/samples/script/_config.js | 13 +++- .../samples/script/expected_map.json | 8 ++ .../_config.js | 12 +++ .../expected_map.json | 8 ++ .../input.svelte | 5 ++ .../output.svelte | 5 ++ .../expected_map.json | 8 ++ .../style-attributes-modified/input.svelte | 6 +- .../style-attributes-modified/output.svelte | 6 +- .../style-attributes/expected_map.json | 8 ++ .../samples/style-attributes/input.svelte | 2 +- 14 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 test/preprocess/samples/script/expected_map.json create mode 100644 test/preprocess/samples/style-attributes-modified-longer/_config.js create mode 100644 test/preprocess/samples/style-attributes-modified-longer/expected_map.json create mode 100644 test/preprocess/samples/style-attributes-modified-longer/input.svelte create mode 100644 test/preprocess/samples/style-attributes-modified-longer/output.svelte create mode 100644 test/preprocess/samples/style-attributes-modified/expected_map.json create mode 100644 test/preprocess/samples/style-attributes/expected_map.json diff --git a/site/content/docs/05-compile-time.md b/site/content/docs/05-compile-time.md index 2992bf0a3d88..bdd1de243e10 100644 --- a/site/content/docs/05-compile-time.md +++ b/site/content/docs/05-compile-time.md @@ -253,7 +253,7 @@ const { code } = await preprocess(source, { return { code: s.toString(), - map: s.generateMap() + map: s.generateMap({ hires: true, file: filename }) } }, style: async ({ content, attributes, filename }) => { diff --git a/src/compiler/preprocess/index.js b/src/compiler/preprocess/index.js index 60546f8319fd..6d03caae3b9f 100644 --- a/src/compiler/preprocess/index.js +++ b/src/compiler/preprocess/index.js @@ -7,7 +7,6 @@ import { } from '../utils/mapped_code.js'; import { decode_map } from './decode_sourcemap.js'; import { replace_in_code, slice_source } from './replace_in_code.js'; -import { regex_whitespaces } from '../utils/patterns.js'; const regex_filepath_separator = /[/\\]/; @@ -132,11 +131,18 @@ function processed_content_to_code(processed, location, file_basename) { * representing the tag content replaced with `processed`. * @param {import('./public.js').Processed} processed * @param {'style' | 'script'} tag_name - * @param {string} attributes + * @param {string} original_attributes + * @param {string} generated_attributes * @param {import('./private.js').Source} source * @returns {MappedCode} */ -function processed_tag_to_code(processed, tag_name, attributes, source) { +function processed_tag_to_code( + processed, + tag_name, + original_attributes, + generated_attributes, + source +) { const { file_basename, get_location } = source; /** @@ -145,16 +151,69 @@ function processed_tag_to_code(processed, tag_name, attributes, source) { */ const build_mapped_code = (code, offset) => MappedCode.from_source(slice_source(code, offset, source)); - const tag_open = `<${tag_name}${attributes || ''}>`; + + // To map the open/close tag and content starts positions correctly, we need to + // differentiate between the original attributes and the generated attributes: + // `source` contains the original attributes and its get_location maps accordingly. + const original_tag_open = `<${tag_name}${original_attributes}>`; + const tag_open = `<${tag_name}${generated_attributes}>`; + /** @type {MappedCode} */ + let tag_open_code; + + if (original_tag_open.length !== tag_open.length) { + // Generate a source map for the open tag + /** @type {import('@ampproject/remapping').DecodedSourceMap['mappings']} */ + const mappings = [ + [ + // start of tag + [0, 0, 0, 0], + // end of tag start + [`<${tag_name}`.length, 0, 0, `<${tag_name}`.length] + ] + ]; + + const line = tag_open.split('\n').length - 1; + const column = tag_open.length - (line === 0 ? 0 : tag_open.lastIndexOf('\n')) - 1; + + while (mappings.length <= line) { + // end of tag start again, if this is a multi line mapping + mappings.push([[0, 0, 0, `<${tag_name}`.length]]); + } + + // end of tag + mappings[line].push([ + column, + 0, + original_tag_open.split('\n').length - 1, + original_tag_open.length - original_tag_open.lastIndexOf('\n') - 1 + ]); + + /** @type {import('@ampproject/remapping').DecodedSourceMap} */ + const map = { + version: 3, + names: [], + sources: [file_basename], + mappings + }; + sourcemap_add_offset(map, get_location(0), 0); + tag_open_code = MappedCode.from_processed(tag_open, map); + } else { + tag_open_code = build_mapped_code(tag_open, 0); + } + const tag_close = ``; - const tag_open_code = build_mapped_code(tag_open, 0); - const tag_close_code = build_mapped_code(tag_close, tag_open.length + source.source.length); + const tag_close_code = build_mapped_code( + tag_close, + original_tag_open.length + source.source.length + ); + parse_attached_sourcemap(processed, tag_name); const content_code = processed_content_to_code( processed, - get_location(tag_open.length), + get_location(original_tag_open.length), file_basename ); + return tag_open_code.concat(content_code).concat(tag_close_code); } @@ -233,6 +292,7 @@ async function process_tag(tag_name, preprocessor, source) { return processed_tag_to_code( processed, tag_name, + attributes, stringify_tag_attributes(processed.attributes) ?? attributes, slice_source(content, tag_offset, source) ); diff --git a/test/preprocess/preprocess.test.js b/test/preprocess/preprocess.test.js index 1615468ee030..3fd8810fa91e 100644 --- a/test/preprocess/preprocess.test.js +++ b/test/preprocess/preprocess.test.js @@ -37,6 +37,14 @@ describe('preprocess', async () => { expect(result.code).toMatchFileSnapshot(`${__dirname}/samples/${dir}/output.svelte`); expect(result.dependencies).toEqual(config.dependencies || []); + + if (fs.existsSync(`${__dirname}/samples/${dir}/expected_map.json`)) { + const expected_map = JSON.parse( + fs.readFileSync(`${__dirname}/samples/${dir}/expected_map.json`, 'utf-8') + ); + // You can use https://sokra.github.io/source-map-visualization/#custom to visualize the source map + expect(result.map).toEqual(expected_map); + } }); } }); diff --git a/test/preprocess/samples/script/_config.js b/test/preprocess/samples/script/_config.js index 1a2104546590..535917ade66d 100644 --- a/test/preprocess/samples/script/_config.js +++ b/test/preprocess/samples/script/_config.js @@ -1,8 +1,17 @@ +import MagicString from 'magic-string'; + export default { preprocess: { - script: ({ content }) => { + script: ({ content, filename }) => { + const s = new MagicString(content); + s.overwrite( + content.indexOf('__THE_ANSWER__'), + content.indexOf('__THE_ANSWER__') + '__THE_ANSWER__'.length, + '42' + ); return { - code: content.replace('__THE_ANSWER__', '42') + code: s.toString(), + map: s.generateMap({ hires: true, file: filename }) }; } } diff --git a/test/preprocess/samples/script/expected_map.json b/test/preprocess/samples/script/expected_map.json new file mode 100644 index 000000000000..dc7a35f1804b --- /dev/null +++ b/test/preprocess/samples/script/expected_map.json @@ -0,0 +1,8 @@ +{ + "version": 3, + "mappings": "AAAA,CAAC,MAAM,CAAC,CAAC;AACT,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAc,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC,MAAM", + "names": [], + "sources": [ + "input.svelte" + ] +} \ No newline at end of file diff --git a/test/preprocess/samples/style-attributes-modified-longer/_config.js b/test/preprocess/samples/style-attributes-modified-longer/_config.js new file mode 100644 index 000000000000..1fbb8b3d23ed --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified-longer/_config.js @@ -0,0 +1,12 @@ +import * as assert from 'node:assert'; + +export default { + preprocess: { + style: ({ attributes }) => { + assert.deepEqual(attributes, { + lang: 'scss' + }); + return { code: 'PROCESSED', attributes: { sth: 'wayyyyyyyyyyyyy looooooonger' } }; + } + } +}; diff --git a/test/preprocess/samples/style-attributes-modified-longer/expected_map.json b/test/preprocess/samples/style-attributes-modified-longer/expected_map.json new file mode 100644 index 000000000000..5d3487863604 --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified-longer/expected_map.json @@ -0,0 +1,8 @@ +{ + "version": 3, + "mappings": "AAAA,GAAG;AACH;AACA,MAAM,mCAAa,UAAM,CAAC,CAAC,KAAK,CAAC;AACjC;AACA,GAAG", + "names": [], + "sources": [ + "input.svelte" + ] +} \ No newline at end of file diff --git a/test/preprocess/samples/style-attributes-modified-longer/input.svelte b/test/preprocess/samples/style-attributes-modified-longer/input.svelte new file mode 100644 index 000000000000..8b2713472a24 --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified-longer/input.svelte @@ -0,0 +1,5 @@ +foo + + + +bar diff --git a/test/preprocess/samples/style-attributes-modified-longer/output.svelte b/test/preprocess/samples/style-attributes-modified-longer/output.svelte new file mode 100644 index 000000000000..e9971f7f1804 --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified-longer/output.svelte @@ -0,0 +1,5 @@ +foo + + + +bar diff --git a/test/preprocess/samples/style-attributes-modified/expected_map.json b/test/preprocess/samples/style-attributes-modified/expected_map.json new file mode 100644 index 000000000000..cc32773847cf --- /dev/null +++ b/test/preprocess/samples/style-attributes-modified/expected_map.json @@ -0,0 +1,8 @@ +{ + "version": 3, + "mappings": "AAAA,GAAG;AACH;AACA,MAAM,WAAiC,UAAM,CAAC,CAAC,KAAK,CAAC;AACrD;AACA,GAAG", + "names": [], + "sources": [ + "input.svelte" + ] +} \ No newline at end of file diff --git a/test/preprocess/samples/style-attributes-modified/input.svelte b/test/preprocess/samples/style-attributes-modified/input.svelte index 2c41c249649a..2b2b0e4423f5 100644 --- a/test/preprocess/samples/style-attributes-modified/input.svelte +++ b/test/preprocess/samples/style-attributes-modified/input.svelte @@ -1 +1,5 @@ - \ No newline at end of file +foo + + + +bar diff --git a/test/preprocess/samples/style-attributes-modified/output.svelte b/test/preprocess/samples/style-attributes-modified/output.svelte index 31191f9fa0bd..ff8ca98bf935 100644 --- a/test/preprocess/samples/style-attributes-modified/output.svelte +++ b/test/preprocess/samples/style-attributes-modified/output.svelte @@ -1 +1,5 @@ - \ No newline at end of file +foo + + + +bar diff --git a/test/preprocess/samples/style-attributes/expected_map.json b/test/preprocess/samples/style-attributes/expected_map.json new file mode 100644 index 000000000000..2eca3ec76305 --- /dev/null +++ b/test/preprocess/samples/style-attributes/expected_map.json @@ -0,0 +1,8 @@ +{ + "version": 3, + "mappings": "AAAA,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,UAAO,CAAC,CAAC,KAAK", + "names": [], + "sources": [ + "input.svelte" + ] +} \ No newline at end of file diff --git a/test/preprocess/samples/style-attributes/input.svelte b/test/preprocess/samples/style-attributes/input.svelte index 3a55a5a3f65d..c513b8bf21ea 100644 --- a/test/preprocess/samples/style-attributes/input.svelte +++ b/test/preprocess/samples/style-attributes/input.svelte @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From bfa4b919585e2e8ccc682e1aeea515d5a837b29a Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 23 May 2023 16:41:30 +0200 Subject: [PATCH 3/5] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7492e1d035b1..3607060b43df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ * **breaking** Deprecate `SvelteComponentTyped`, use `SvelteComponent` instead ([#8512](https://github.com/sveltejs/svelte/pull/8512)) * **breaking** Error on falsy values instead of stores passed to `derived` ([#7947](https://github.com/sveltejs/svelte/pull/7947)) * **breaking** Custom store implementers now need to pass an `update` function additionally to the `set` function ([#6750](https://github.com/sveltejs/svelte/pull/6750)) +* **breaking** Change order in which preprocessors are applied ([#8618](https://github.com/sveltejs/svelte/pull/8618)) +* Add a way to modify attributes for script/style preprocessors ([#8618](https://github.com/sveltejs/svelte/pull/8618)) * Improve hydration speed by adding `data-svelte-h` attribute to detect unchanged HTML elements ([#7426](https://github.com/sveltejs/svelte/pull/7426)) * Add `a11y no-noninteractive-element-interactions` rule ([#8391](https://github.com/sveltejs/svelte/pull/8391)) * Add `a11y-no-static-element-interactions`rule ([#8251](https://github.com/sveltejs/svelte/pull/8251)) From 1030d2a8207628e20f7d8e2e54919df46d4531d8 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 23 May 2023 16:46:18 +0200 Subject: [PATCH 4/5] fix tests --- test/preprocess/preprocess.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/preprocess/preprocess.test.js b/test/preprocess/preprocess.test.js index 3fd8810fa91e..011db0f9af4e 100644 --- a/test/preprocess/preprocess.test.js +++ b/test/preprocess/preprocess.test.js @@ -43,7 +43,7 @@ describe('preprocess', async () => { fs.readFileSync(`${__dirname}/samples/${dir}/expected_map.json`, 'utf-8') ); // You can use https://sokra.github.io/source-map-visualization/#custom to visualize the source map - expect(result.map).toEqual(expected_map); + expect(JSON.parse(JSON.stringify(result.map))).toEqual(expected_map); } }); } From 3def0762de58554c2ca100b37f6db4a82e1eedef Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 23 May 2023 16:54:13 +0200 Subject: [PATCH 5/5] \r\n vs \n strikes again --- test/preprocess/preprocess.test.js | 4 +++- test/preprocess/samples/script/expected_map.json | 2 +- .../style-attributes-modified-longer/expected_map.json | 2 +- .../samples/style-attributes-modified/expected_map.json | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/preprocess/preprocess.test.js b/test/preprocess/preprocess.test.js index 011db0f9af4e..a11f6492ec40 100644 --- a/test/preprocess/preprocess.test.js +++ b/test/preprocess/preprocess.test.js @@ -18,7 +18,9 @@ describe('preprocess', async () => { const it_fn = skip ? it.skip : solo ? it.only : it; it_fn(dir, async ({ expect }) => { - const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8'); + const input = fs + .readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8') + .replace(/\r\n/g, '\n'); const result = await svelte.preprocess( input, diff --git a/test/preprocess/samples/script/expected_map.json b/test/preprocess/samples/script/expected_map.json index dc7a35f1804b..d5bf98483f78 100644 --- a/test/preprocess/samples/script/expected_map.json +++ b/test/preprocess/samples/script/expected_map.json @@ -1,6 +1,6 @@ { "version": 3, - "mappings": "AAAA,CAAC,MAAM,CAAC,CAAC;AACT,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAc,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC,MAAM", + "mappings": "AAAA,CAAC,MAAM,CAAC;AACR,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAc,CAAC,CAAC;AAC7B,CAAC,CAAC,MAAM", "names": [], "sources": [ "input.svelte" diff --git a/test/preprocess/samples/style-attributes-modified-longer/expected_map.json b/test/preprocess/samples/style-attributes-modified-longer/expected_map.json index 5d3487863604..28397f95d9b1 100644 --- a/test/preprocess/samples/style-attributes-modified-longer/expected_map.json +++ b/test/preprocess/samples/style-attributes-modified-longer/expected_map.json @@ -1,6 +1,6 @@ { "version": 3, - "mappings": "AAAA,GAAG;AACH;AACA,MAAM,mCAAa,UAAM,CAAC,CAAC,KAAK,CAAC;AACjC;AACA,GAAG", + "mappings": "AAAA;;AAEA,MAAM,mCAAa,UAAM,CAAC,CAAC,KAAK;;AAEhC", "names": [], "sources": [ "input.svelte" diff --git a/test/preprocess/samples/style-attributes-modified/expected_map.json b/test/preprocess/samples/style-attributes-modified/expected_map.json index cc32773847cf..65f340c1c04e 100644 --- a/test/preprocess/samples/style-attributes-modified/expected_map.json +++ b/test/preprocess/samples/style-attributes-modified/expected_map.json @@ -1,6 +1,6 @@ { "version": 3, - "mappings": "AAAA,GAAG;AACH;AACA,MAAM,WAAiC,UAAM,CAAC,CAAC,KAAK,CAAC;AACrD;AACA,GAAG", + "mappings": "AAAA;;AAEA,MAAM,WAAiC,UAAM,CAAC,CAAC,KAAK;;AAEpD", "names": [], "sources": [ "input.svelte"