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

@ -16,10 +16,12 @@ describe.only('preprocess', () => {
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 actual = await svelte.preprocess(input, config.preprocess);
fs.writeFileSync(`test/preprocess/samples/${dir}/_actual.html`, actual);
const result = await svelte.preprocess(input, config.preprocess);
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