Merge pull request #969 from sveltejs/css-preprocessing

Css preprocessing
pull/970/head
Rich Harris 7 years ago committed by GitHub
commit fd77c40825
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,7 +4,8 @@ import generate from './generators/dom/index';
import generateSSR from './generators/server-side-rendering/index';
import { assign } from './shared/index.js';
import Stylesheet from './css/Stylesheet';
import { Parsed, CompileOptions, Warning } from './interfaces';
import { Parsed, CompileOptions, Warning, PreprocessOptions, Preprocessor } from './interfaces';
import { SourceMap } from 'magic-string';
const version = '__VERSION__';
@ -34,9 +35,74 @@ function defaultOnerror(error: Error) {
throw error;
}
function parseAttributeValue(value: string) {
return /^['"]/.test(value) ?
value.slice(1, -1) :
value;
}
function parseAttributes(str: string) {
const attrs = {};
str.split(/\s+/).filter(Boolean).forEach(attr => {
const [name, value] = attr.split('=');
attrs[name] = value ? parseAttributeValue(value) : true;
});
return attrs;
}
async function replaceTagContents(source, type: 'script' | 'style', preprocessor: Preprocessor) {
const exp = new RegExp(`<${type}([\\S\\s]*?)>([\\S\\s]*?)<\\/${type}>`, 'ig');
const match = exp.exec(source);
if (match) {
const attributes: Record<string, string | boolean> = parseAttributes(match[1]);
const content: string = match[2];
const processed: { code: string, map?: SourceMap | string } = await preprocessor({
content,
attributes
});
if (processed && processed.code) {
return (
source.slice(0, match.index) +
`<${type}>${processed.code}</${type}>` +
source.slice(match.index + match[0].length)
);
}
}
return source;
}
export async function preprocess(source: string, options: PreprocessOptions) {
const { markup, style, script } = options;
if (!!markup) {
const processed: { code: string, map?: SourceMap | string } = await markup({ content: source });
source = processed.code;
}
if (!!style) {
source = await replaceTagContents(source, 'style', style);
}
if (!!script) {
source = await replaceTagContents(source, 'script', script);
}
return {
// TODO return separated output, in future version where svelte.compile supports it:
// style: { code: styleCode, map: styleMap },
// script { code: scriptCode, map: scriptMap },
// markup { code: markupCode, map: markupMap },
toString() {
return source;
}
};
}
export function compile(source: string, _options: CompileOptions) {
const options = normalizeOptions(_options);
let parsed: Parsed;
try {
@ -53,7 +119,7 @@ export function compile(source: string, _options: CompileOptions) {
const compiler = options.generate === 'ssr' ? generateSSR : generate;
return compiler(parsed, source, stylesheet, options);
}
};
export function create(source: string, _options: CompileOptions = {}) {
_options.format = 'eval';
@ -65,7 +131,7 @@ export function create(source: string, _options: CompileOptions = {}) {
}
try {
return (0,eval)(compiled.code);
return (0, eval)(compiled.code);
} catch (err) {
if (_options.onerror) {
_options.onerror(err);

@ -1,3 +1,5 @@
import {SourceMap} from 'magic-string';
export interface Node {
start: number;
end: number;
@ -78,4 +80,12 @@ export interface Visitor {
export interface CustomElementOptions {
tag?: string;
props?: string[];
}
}
export interface PreprocessOptions {
markup?: (options: {content: string}) => { code: string, map?: SourceMap | string };
style?: Preprocessor;
script?: Preprocessor;
}
export type Preprocessor = (options: {content: string, attributes: Record<string, string | boolean>}) => { code: string, map?: SourceMap | string };

@ -0,0 +1,148 @@
import assert from 'assert';
import {svelte} from '../helpers.js';
describe('preprocess', () => {
it('preprocesses entire component', () => {
const source = `
<h1>Hello __NAME__!</h1>
`;
const expected = `
<h1>Hello world!</h1>
`;
return svelte.preprocess(source, {
markup: ({ content }) => {
return {
code: content.replace('__NAME__', 'world')
};
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('preprocesses style', () => {
const source = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: $brand;
}
</style>
`;
const expected = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: purple;
}
</style>
`;
return svelte.preprocess(source, {
style: ({ content }) => {
return {
code: content.replace('$brand', 'purple')
};
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('preprocesses style asynchronously', () => {
const source = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: $brand;
}
</style>
`;
const expected = `
<div class='brand-color'>$brand</div>
<style>
.brand-color {
color: purple;
}
</style>
`;
return svelte.preprocess(source, {
style: ({ content }) => {
return Promise.resolve({
code: content.replace('$brand', 'purple')
});
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('preprocesses script', () => {
const source = `
<script>
console.log(__THE_ANSWER__);
</script>
`;
const expected = `
<script>
console.log(42);
</script>
`;
return svelte.preprocess(source, {
script: ({ content }) => {
return {
code: content.replace('__THE_ANSWER__', '42')
};
}
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
it('parses attributes', () => {
const source = `
<style type='text/scss' data-foo="bar" bool></style>
`;
return svelte.preprocess(source, {
style: ({ attributes }) => {
assert.deepEqual(attributes, {
type: 'text/scss',
'data-foo': 'bar',
bool: true
});
}
});
});
it('ignores null/undefined returned from preprocessor', () => {
const source = `
<script>
console.log('ignore me');
</script>
`;
const expected = `
<script>
console.log('ignore me');
</script>
`;
return svelte.preprocess(source, {
script: () => null
}).then(processed => {
assert.equal(processed.toString(), expected);
});
});
});

@ -64,8 +64,8 @@ ajv@^4.9.1:
json-stable-stringify "^1.0.1"
ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.0.tgz#eb2840746e9dc48bd5e063a36e3fd400c5eab5a9"
version "5.5.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.1.tgz#b38bb8876d9e86bee994956a04e721e88b248eb2"
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
@ -538,8 +538,8 @@ commander@2.9.0:
graceful-readlink ">= 1.0.0"
commander@^2.9.0:
version "2.12.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.1.tgz#468635c4168d06145b9323356d1da84d14ac4a7a"
version "2.12.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
commondir@^1.0.1:
version "1.0.1"
@ -887,8 +887,8 @@ eslint-scope@^3.7.1:
estraverse "^4.1.1"
eslint@^4.3.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.12.0.tgz#a7ce78eba8cc8f2443acfbbc870cc31a65135884"
version "4.12.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.12.1.tgz#5ec1973822b4a066b353770c3c6d69a2a188e880"
dependencies:
ajv "^5.3.0"
babel-code-frame "^6.22.0"
@ -1027,10 +1027,14 @@ extract-zip@^1.0.3:
mkdirp "0.5.0"
yauzl "2.4.1"
extsprintf@1.3.0, extsprintf@^1.2.0:
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
@ -1600,8 +1604,8 @@ is-path-in-cwd@^1.0.0:
is-path-inside "^1.0.0"
is-path-inside@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f"
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
dependencies:
path-is-inside "^1.0.1"
@ -1872,8 +1876,8 @@ load-json-file@^2.0.0:
strip-bom "^3.0.0"
locate-character@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-2.0.1.tgz#48f9599f342daf26f73db32f45941eae37bae391"
version "2.0.3"
resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-2.0.3.tgz#85a5aedae26b3536c3e97016af164cdaa3ae5ae1"
locate-path@^2.0.0:
version "2.0.0"
@ -3292,8 +3296,8 @@ typescript@^1.8.9:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-1.8.10.tgz#b475d6e0dff0bf50f296e5ca6ef9fbb5c7320f1e"
typescript@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631"
version "2.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
uglify-js@^2.6:
version "2.8.29"

Loading…
Cancel
Save