diff --git a/site/content/docs/04-compile-time.md b/site/content/docs/04-compile-time.md index f753b2fcfeda..262639d1daa3 100644 --- a/site/content/docs/04-compile-time.md +++ b/site/content/docs/04-compile-time.md @@ -203,6 +203,7 @@ result: { }>, options?: { filename?: string + strictOrder?: boolean } ) ``` @@ -305,6 +306,30 @@ const { code } = svelte.preprocess(source, [ }); ``` +--- + +User may override default preprocessor order by passing `strictOrder` option. + +```js +const svelte = require('svelte/compiler'); + +const { code } = svelte.preprocess(source, [ + { + style: () => { + console.log('this runs first'); + } + }, + { + markup: () => { + console.log('this runs second'); + } + } +], { + filename: 'App.svelte', + strictOrder: true +}); +``` + ### `svelte.walk` diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 12b161aeeb0e..781bad71e92b 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -26,7 +26,8 @@ const valid_options = [ 'css', 'loopGuardTimeout', 'preserveComments', - 'preserveWhitespace' + 'preserveWhitespace', + 'strictOrder' ]; function validate_options(options: CompileOptions, warnings: Warning[]) { diff --git a/src/compiler/preprocess/index.ts b/src/compiler/preprocess/index.ts index 1a13b869e7af..60609f02753f 100644 --- a/src/compiler/preprocess/index.ts +++ b/src/compiler/preprocess/index.ts @@ -67,68 +67,144 @@ async function replace_async(str: string, re: RegExp, func: (...any) => Promise< return out; } +async function process_markup( + source: string, + func: (...any) => Processed | Promise, + filename: string, +) { + + const processed: Processed = await func({ + content: source, + filename + }); + + return { + source: processed ? processed.code : source, + dependencies: processed.dependencies + }; +} + +async function process_script( + source: string, + func: (...any) => Processed | Promise, + filename: string, +) { + const dependencies = []; + + source = await replace_async( + source, + /|([^]*?)<\/script>/gi, + async (match, attributes = '', content) => { + if (!attributes && !content) { + return match; + } + attributes = attributes || ''; + const processed: Processed = await func({ + content, + attributes: parse_attributes(attributes), + filename + }); + if (processed && processed.dependencies) { + dependencies.push(...processed.dependencies); + } + + return processed ? `${processed.code}` : match; + } + ); + + return { + source, + dependencies, + }; +} + +async function process_style( + source: string, + func: (...any) => Processed | Promise, + filename: string, +) { + const dependencies = []; + + source = await replace_async( + source, + /|([^]*?)<\/style>/gi, + async (match, attributes = '', content) => { + if (!attributes && !content) { + return match; + } + const processed: Processed = await func({ + content, + attributes: parse_attributes(attributes), + filename + }); + + if (processed && processed.dependencies) { + dependencies.push(...processed.dependencies); + } + + return processed ? `${processed.code}` : match; + } + ); + + return { + source, + dependencies, + }; +} + +async function async_for_each(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} + export default async function preprocess( source: string, preprocessor: PreprocessorGroup | PreprocessorGroup[], - options?: { filename?: string } + options?: { filename?: string; strictOrder?: boolean } ) { // @ts-ignore todo: doublecheck const filename = (options && options.filename) || preprocessor.filename; // legacy + const strictOrder = options && options.strictOrder; const dependencies = []; const preprocessors = 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); - - for (const fn of markup) { - const processed = await fn({ - content: source, - filename - }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - source = processed ? processed.code : source; - } + const order = strictOrder + ? preprocessors + : [ + ...preprocessors.map(({ markup }) => ({ markup })), + ...preprocessors.map(({ script }) => ({ script })), + ...preprocessors.map(({ style }) => ({ style })), + ]; + + await async_for_each(order, async p => { + let processed; - for (const fn of script) { - source = await replace_async( - source, - /|([^]*?)<\/script>/gi, - async (match, attributes = '', content) => { - if (!attributes && !content) { - return match; - } - attributes = attributes || ''; - const processed = await fn({ - content, - attributes: parse_attributes(attributes), - filename - }); - if (processed && processed.dependencies) dependencies.push(...processed.dependencies); - return processed ? `${processed.code}` : match; + if (p.markup) { + processed = await process_markup(source, p.markup, filename); + source = processed.source; + if (processed.dependencies && processed.dependencies.length) { + dependencies.push(...processed.dependencies); } - ); - } + } - 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; + if (p.script) { + processed = await process_script(source, p.script, filename); + source = processed.source; + if (processed.dependencies && processed.dependencies.length) { + dependencies.push(...processed.dependencies); } - ); - } + } + + if (p.style) { + processed = await process_style(source, p.style, filename); + source = processed.source; + if (processed.dependencies && processed.dependencies.length) { + dependencies.push(...processed.dependencies); + } + } + }); return { // TODO return separated output, in future version where svelte.compile supports it: diff --git a/test/preprocess/index.js b/test/preprocess/index.js index 5d83bb60590d..a3b37b00f0f9 100644 --- a/test/preprocess/index.js +++ b/test/preprocess/index.js @@ -14,10 +14,20 @@ describe('preprocess', () => { } (config.skip ? it.skip : solo ? it.only : it)(dir, async () => { - const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8'); - const expected = fs.readFileSync(`${__dirname}/samples/${dir}/output.svelte`, 'utf-8'); + const input = fs.readFileSync( + `${__dirname}/samples/${dir}/input.svelte`, + 'utf-8' + ); + const expected = fs.readFileSync( + `${__dirname}/samples/${dir}/output.svelte`, + 'utf-8' + ); - const result = await svelte.preprocess(input, config.preprocess); + const result = await svelte.preprocess( + input, + config.preprocess, + config.options + ); fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, result.code); assert.equal(result.code, expected); diff --git a/test/preprocess/samples/normal-order/_config.js b/test/preprocess/samples/normal-order/_config.js new file mode 100644 index 000000000000..cbac27884385 --- /dev/null +++ b/test/preprocess/samples/normal-order/_config.js @@ -0,0 +1,13 @@ +export default { + options: { + strictOrder: false, + }, + preprocess: [ + { + style: ({ content }) => ({ code: content.replace(/one/g, 'two') }), + }, + { + markup: ({ content }) => ({ code: content.replace(/two/g, 'three') }), + }, + ], +}; diff --git a/test/preprocess/samples/normal-order/input.svelte b/test/preprocess/samples/normal-order/input.svelte new file mode 100644 index 000000000000..6dbbfb47a9cf --- /dev/null +++ b/test/preprocess/samples/normal-order/input.svelte @@ -0,0 +1,11 @@ +

one

+ + + + \ No newline at end of file diff --git a/test/preprocess/samples/normal-order/output.svelte b/test/preprocess/samples/normal-order/output.svelte new file mode 100644 index 000000000000..1ca1a0d4c1c8 --- /dev/null +++ b/test/preprocess/samples/normal-order/output.svelte @@ -0,0 +1,11 @@ +

one

+ + + + \ No newline at end of file diff --git a/test/preprocess/samples/strict-order/_config.js b/test/preprocess/samples/strict-order/_config.js new file mode 100644 index 000000000000..faa6371149e7 --- /dev/null +++ b/test/preprocess/samples/strict-order/_config.js @@ -0,0 +1,13 @@ +export default { + options: { + strictOrder: true, + }, + preprocess: [ + { + style: ({ content }) => ({ code: content.replace(/one/g, 'two') }), + }, + { + markup: ({ content }) => ({ code: content.replace(/two/g, 'three') }), + }, + ], +}; diff --git a/test/preprocess/samples/strict-order/input.svelte b/test/preprocess/samples/strict-order/input.svelte new file mode 100644 index 000000000000..6dbbfb47a9cf --- /dev/null +++ b/test/preprocess/samples/strict-order/input.svelte @@ -0,0 +1,11 @@ +

one

+ + + + \ No newline at end of file diff --git a/test/preprocess/samples/strict-order/output.svelte b/test/preprocess/samples/strict-order/output.svelte new file mode 100644 index 000000000000..3bf89c6b959b --- /dev/null +++ b/test/preprocess/samples/strict-order/output.svelte @@ -0,0 +1,11 @@ +

one

+ + + + \ No newline at end of file