support multiple preprocessors, handle dependencies - fixes #1973

pull/1981/head
Richard Harris 6 years ago committed by Chris Reeves
parent dcfe85ecdb
commit 4936cd5ac4

@ -1,25 +1,25 @@
import { SourceMap } from 'magic-string'; import { SourceMap } from 'magic-string';
import replaceAsync from '../utils/replaceAsync'; import replaceAsync from '../utils/replaceAsync';
export interface PreprocessOptions { export interface PreprocessorGroup {
markup?: (options: { markup?: (options: {
content: string, content: string,
filename: string filename: string
}) => { code: string, map?: SourceMap | string }; }) => { code: string, map?: SourceMap | string, dependencies?: string[] };
style?: Preprocessor; style?: Preprocessor;
script?: Preprocessor; script?: Preprocessor;
filename?: string
} }
export type Preprocessor = (options: { export type Preprocessor = (options: {
content: string, content: string,
attributes: Record<string, string | boolean>, attributes: Record<string, string | boolean>,
filename?: string filename?: string
}) => { code: string, map?: SourceMap | string }; }) => { code: string, map?: SourceMap | string, dependencies?: string[] };
interface Processed { interface Processed {
code: string; code: string;
map?: SourceMap | string; map?: SourceMap | string;
dependencies?: string[];
} }
function parseAttributeValue(value: string) { function parseAttributeValue(value: string) {
@ -39,28 +39,55 @@ function parseAttributes(str: string) {
export default async function preprocess( export default async function preprocess(
source: string, source: string,
options: PreprocessOptions preprocessor: PreprocessorGroup | PreprocessorGroup[],
options?: { filename?: string }
) { ) {
if (options.markup) { const filename = (options && options.filename) || preprocessor.filename; // legacy
const processed: Processed = await options.markup({ 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: Processed = await fn({
content: source, content: source,
filename: options.filename, filename
}); });
if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
source = processed ? processed.code : source; source = processed ? processed.code : source;
} }
if (options.style || options.script) {
for (const fn of script) {
source = await replaceAsync(
source,
/<script([^]*?)>([^]*?)<\/script>/gi,
async (match, attributes, content) => {
const processed: Processed = await fn({
content,
attributes: parseAttributes(attributes),
filename
});
if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
return processed ? `<script${attributes}>${processed.code}</script>` : match;
}
);
}
for (const fn of style) {
source = await replaceAsync( source = await replaceAsync(
source, source,
/<(script|style)([^]*?)>([^]*?)<\/\1>/gi, /<style([^]*?)>([^]*?)<\/style>/gi,
async (match, type, attributes, content) => { async (match, attributes, content) => {
const processed: Processed = const processed: Processed = await fn({
type in options && content,
(await options[type]({ attributes: parseAttributes(attributes),
content, filename
attributes: parseAttributes(attributes), });
filename: options.filename, if (processed && processed.dependencies) dependencies.push(...processed.dependencies);
})); return processed ? `<style${attributes}>${processed.code}</style>` : match;
return processed ? `<${type}${attributes}>${processed.code}</${type}>` : match;
} }
); );
} }
@ -71,6 +98,9 @@ export default async function preprocess(
// script { code: scriptCode, map: scriptMap }, // script { code: scriptCode, map: scriptMap },
// markup { code: markupCode, map: markupMap }, // markup { code: markupCode, map: markupMap },
code: source,
dependencies: [...new Set(dependencies)],
toString() { toString() {
return source; return source;
} }

@ -16,10 +16,12 @@ describe.only('preprocess', () => {
const input = fs.readFileSync(`test/preprocess/samples/${dir}/input.html`, 'utf-8'); const input = fs.readFileSync(`test/preprocess/samples/${dir}/input.html`, 'utf-8');
const expected = fs.readFileSync(`test/preprocess/samples/${dir}/output.html`, 'utf-8'); const expected = fs.readFileSync(`test/preprocess/samples/${dir}/output.html`, 'utf-8');
const actual = await svelte.preprocess(input, config.preprocess); const result = await svelte.preprocess(input, config.preprocess);
fs.writeFileSync(`test/preprocess/samples/${dir}/_actual.html`, actual); fs.writeFileSync(`test/preprocess/samples/${dir}/_actual.html`, result.code);
assert.equal(actual, expected); assert.equal(result.code, expected);
assert.deepEqual(result.dependencies, config.dependencies || []);
}); });
}); });
}); });

@ -0,0 +1,15 @@
export default {
preprocess: {
style: ({ content }) => {
const dependencies = [];
const code = content.replace(/@import '(.+)';/g, (match, $1) => {
dependencies.push($1);
return '/* removed */';
});
return { code, dependencies };
}
},
dependencies: ['./foo.css']
};

@ -0,0 +1,3 @@
<style>
@import './foo.css';
</style>

@ -0,0 +1,3 @@
<style>
/* removed */
</style>

@ -0,0 +1,14 @@
export default {
preprocess: [
{
markup: ({ content }) => ({ code: content.replace(/one/g, 'two') }),
script: ({ content }) => ({ code: content.replace(/three/g, 'four') }),
style: ({ content }) => ({ code: content.replace(/four/g, 'five') })
},
{
markup: ({ content }) => ({ code: content.replace(/two/g, 'three') }),
script: ({ content }) => ({ code: content.replace(/four/g, 'five') }),
style: ({ content }) => ({ code: content.replace(/three/g, 'four') })
}
]
};

@ -0,0 +1,11 @@
<p>one</p>
<style>
.one {
color: red;
}
</style>
<script>
console.log('one');
</script>

@ -0,0 +1,11 @@
<p>three</p>
<style>
.four {
color: red;
}
</style>
<script>
console.log('five');
</script>
Loading…
Cancel
Save