diff --git a/packages/svelte/src/compiler/compile/css/Stylesheet.js b/packages/svelte/src/compiler/compile/css/Stylesheet.js index d6bf41c13672..3fd264fbc0c8 100644 --- a/packages/svelte/src/compiler/compile/css/Stylesheet.js +++ b/packages/svelte/src/compiler/compile/css/Stylesheet.js @@ -86,12 +86,13 @@ class Rule { /** * @param {import('magic-string').default} code * @param {boolean} _dev + * @param {boolean} preserveUnusedSelectors */ - minify(code, _dev) { + minify(code, _dev, preserveUnusedSelectors) { let c = this.node.start; let started = false; this.selectors.forEach((selector) => { - if (selector.used) { + if (preserveUnusedSelectors === true || selector.used) { const separator = started ? ',' : ''; if (selector.node.start - c > separator.length) { code.update(c, selector.node.start, separator); @@ -229,8 +230,9 @@ class Atrule { /** * @param {import('magic-string').default} code * @param {boolean} dev + * @param {boolean} _preserveUnusedSelectors */ - minify(code, dev) { + minify(code, dev, _preserveUnusedSelectors) { if (this.node.name === 'media') { const expression_char = code.original[this.node.prelude.start]; let c = this.node.start + (expression_char === '(' ? 6 : 7); @@ -449,8 +451,11 @@ export default class Stylesheet { }); } - /** @param {string} file */ - render(file) { + /** + * @param {string} file + * @param {boolean} preserveUnusedSelectors + */ + render(file, preserveUnusedSelectors) { if (!this.has_styles) { return { code: null, map: null }; } @@ -469,9 +474,9 @@ export default class Stylesheet { }); let c = 0; this.children.forEach((child) => { - if (child.is_used(this.dev)) { + if (preserveUnusedSelectors === true || child.is_used(this.dev)) { code.remove(c, child.node.start); - child.minify(code, this.dev); + child.minify(code, this.dev, preserveUnusedSelectors); c = child.node.end; } }); diff --git a/packages/svelte/src/compiler/compile/index.js b/packages/svelte/src/compiler/compile/index.js index 09bfc2996fd3..9270821fcd16 100644 --- a/packages/svelte/src/compiler/compile/index.js +++ b/packages/svelte/src/compiler/compile/index.js @@ -31,7 +31,8 @@ const valid_options = [ 'preserveComments', 'preserveWhitespace', 'cssHash', - 'discloseVersion' + 'discloseVersion', + 'preserveUnusedSelectors' ]; const valid_css_values = [true, false, 'injected', 'external', 'none']; const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/; @@ -126,7 +127,13 @@ function validate_options(options, warnings) { */ export default function compile(source, options = {}) { options = Object.assign( - { generate: 'dom', dev: false, enableSourcemap: true, css: 'injected' }, + { + generate: 'dom', + dev: false, + enableSourcemap: true, + css: 'injected', + preserveUnusedSelectors: false + }, options ); const stats = new Stats(); diff --git a/packages/svelte/src/compiler/compile/render_dom/index.js b/packages/svelte/src/compiler/compile/render_dom/index.js index 0627e7e0cb58..522a05497113 100644 --- a/packages/svelte/src/compiler/compile/render_dom/index.js +++ b/packages/svelte/src/compiler/compile/render_dom/index.js @@ -24,7 +24,7 @@ export default function dom(component, options) { const file = component.file ? x`"${component.file}"` : x`undefined`; body.push(b`const ${renderer.file_var} = ${file};`); } - const css = component.stylesheet.render(options.filename); + const css = component.stylesheet.render(options.filename, options.preserveUnusedSelectors); const css_sourcemap_enabled = check_enable_sourcemap(options.enableSourcemap, 'css'); if (css_sourcemap_enabled) { css.map = apply_preprocessor_sourcemap( diff --git a/packages/svelte/src/compiler/compile/render_ssr/index.js b/packages/svelte/src/compiler/compile/render_ssr/index.js index 7a48c7f7e38f..0f5cf34af02f 100644 --- a/packages/svelte/src/compiler/compile/render_ssr/index.js +++ b/packages/svelte/src/compiler/compile/render_ssr/index.js @@ -31,7 +31,7 @@ export default function ssr(component, options) { // TODO concatenate CSS maps const css = options.customElement ? { code: null, map: null } - : component.stylesheet.render(options.filename); + : component.stylesheet.render(options.filename, options.preserveUnusedSelectors); const uses_rest = component.var_lookup.has('$$restProps'); const props = component.vars.filter((variable) => !variable.module && variable.export_name); const rest = uses_rest diff --git a/packages/svelte/src/compiler/interfaces.d.ts b/packages/svelte/src/compiler/interfaces.d.ts index 64be9698f5b3..b5c650cdd03e 100644 --- a/packages/svelte/src/compiler/interfaces.d.ts +++ b/packages/svelte/src/compiler/interfaces.d.ts @@ -350,6 +350,13 @@ export interface CompileOptions { * @default true */ discloseVersion?: boolean; + + /** + * If `true`, unused stylesheet selectors will be kept instead of removed. + * + * @default false + */ + preserveUnusedSelectors?: boolean; } export interface ParserOptions { diff --git a/packages/svelte/test/css/samples/preserve-unused-selector/_config.js b/packages/svelte/test/css/samples/preserve-unused-selector/_config.js new file mode 100644 index 000000000000..8bf5058a9813 --- /dev/null +++ b/packages/svelte/test/css/samples/preserve-unused-selector/_config.js @@ -0,0 +1,30 @@ +export default { + compileOptions: { + preserveUnusedSelectors: true + }, + warnings: [ + { + filename: 'SvelteComponent.svelte', + code: 'css-unused-selector', + message: 'Unused CSS selector ".bar"', + start: { + line: 8, + column: 1, + character: 60 + }, + end: { + line: 8, + column: 5, + character: 64 + }, + pos: 60, + frame: ` + 6: } + 7: + 8: .bar { + ^ + 9: color: blue; + 10: }` + } + ] +}; diff --git a/packages/svelte/test/css/samples/preserve-unused-selector/expected.css b/packages/svelte/test/css/samples/preserve-unused-selector/expected.css new file mode 100644 index 000000000000..5633dd783fdd --- /dev/null +++ b/packages/svelte/test/css/samples/preserve-unused-selector/expected.css @@ -0,0 +1 @@ +.foo.svelte-xyz{color:red}.bar{color:blue} \ No newline at end of file diff --git a/packages/svelte/test/css/samples/preserve-unused-selector/expected.html b/packages/svelte/test/css/samples/preserve-unused-selector/expected.html new file mode 100644 index 000000000000..cfad41216efe --- /dev/null +++ b/packages/svelte/test/css/samples/preserve-unused-selector/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/packages/svelte/test/css/samples/preserve-unused-selector/input.svelte b/packages/svelte/test/css/samples/preserve-unused-selector/input.svelte new file mode 100644 index 000000000000..1d62e3a67577 --- /dev/null +++ b/packages/svelte/test/css/samples/preserve-unused-selector/input.svelte @@ -0,0 +1,11 @@ +
+ + \ No newline at end of file