Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Compiler option css: 'none' support #7914

Merged
merged 7 commits into from Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion site/content/docs/05-compile-time.md
Expand Up @@ -73,7 +73,7 @@ The following options can be passed to the compiler. None are required:
| `accessors` | `false` | If `true`, getters and setters will be created for the component's props. If `false`, they will only be created for readonly exported values (i.e. those declared with `const`, `class` and `function`). If compiling with `customElement: true` this option defaults to `true`.
| `customElement` | `false` | If `true`, tells the compiler to generate a custom element constructor instead of a regular Svelte component.
| `tag` | `null` | A `string` that tells Svelte what tag name to register the custom element with. It must be a lowercase alphanumeric string with at least one hyphen, e.g. `"my-element"`.
| `css` | `true` | If `true`, styles will be included in the JavaScript class and injected at runtime for the components actually rendered. If `false`, the CSS will be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `false` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files.
| `css` | `'injected'` | If `'injected'` (formerly `true`), styles will be included in the JavaScript class and injected at runtime for the components actually rendered. If `'external'` (formerly `false`), the CSS will be returned in the `css` field of the compilation result. Most Svelte bundler plugins will set this to `'external'` and use the CSS that is statically generated for better performance, as it will result in smaller JavaScript bundles and the output can be served as cacheable `.css` files. If `'none'`, styles are completely avoided and no CSS output is generated.
| `cssHash` | See right | A function that takes a `{ hash, css, name, filename }` argument and returns the string that is used as a classname for scoped CSS. It defaults to returning `svelte-${hash(css)}`
| `loopGuardTimeout` | 0 | A `number` that tells Svelte to break the loop if it blocks the thread for more than `loopGuardTimeout` ms. This is useful to prevent infinite loops. **Only available when `dev: true`**
| `preserveComments` | `false` | If `true`, your HTML comments will be preserved during server-side rendering. By default, they are stripped out.
Expand Down
27 changes: 25 additions & 2 deletions src/compiler/compile/index.ts
Expand Up @@ -35,11 +35,19 @@ const valid_options = [
'cssHash'
];

const valid_css_values = [
true,
false,
'injected',
'external',
'none'
];

const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
const regex_starts_with_lowercase_character = /^[a-z]/;

function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev, namespace } = options;
const { name, filename, loopGuardTimeout, dev, namespace, css } = options;

Object.keys(options).forEach(key => {
if (!valid_options.includes(key)) {
Expand Down Expand Up @@ -75,6 +83,21 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
});
}

if (valid_css_values.indexOf(css) === -1) {
throw new Error(`options.css must be true, false, 'injected', 'external', or 'none' (got '${css}')`);
}

if (css === true || css === false) {
options.css = css === true ? 'injected' : 'external';
const message = `options.css as a boolean is deprecated. Use '${options.css}' instead of ${css}.`;
warnings.push({
code: 'options-css-boolean-deprecated',
message,
filename,
toString: () => message
});
}

if (namespace && valid_namespaces.indexOf(namespace) === -1) {
const match = fuzzymatch(namespace, valid_namespaces);
if (match) {
Expand All @@ -86,7 +109,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
}

export default function compile(source: string, options: CompileOptions = {}) {
options = Object.assign({ generate: 'dom', dev: false, enableSourcemap: true }, options);
options = Object.assign({ generate: 'dom', dev: false, enableSourcemap: true, css: 'injected' }, options);

const stats = new Stats();
const warnings = [];
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/compile/render_dom/index.ts
Expand Up @@ -54,7 +54,7 @@ export default function dom(
const should_add_css = (
!options.customElement &&
!!styles &&
options.css !== false
options.css === 'injected'
);

if (should_add_css) {
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/interfaces.ts
Expand Up @@ -179,7 +179,7 @@ export interface CompileOptions {
legacy?: boolean;
customElement?: boolean;
tag?: string;
css?: boolean;
css?: 'injected' | 'external' | 'none' | boolean;
loopGuardTimeout?: number;
namespace?: string;
cssHash?: CssHashGetter;
Expand All @@ -191,6 +191,7 @@ export interface CompileOptions {
export interface ParserOptions {
filename?: string;
customElement?: boolean;
css?: 'injected' | 'external' | 'none' | boolean;
}

export interface Visitor {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/parse/index.ts
Expand Up @@ -21,6 +21,7 @@ export class Parser {
readonly template: string;
readonly filename?: string;
readonly customElement: boolean;
readonly css_mode: 'injected' | 'external' | 'none' | boolean;

index = 0;
stack: TemplateNode[] = [];
Expand All @@ -39,6 +40,7 @@ export class Parser {
this.template = template.trimRight();
this.filename = options.filename;
this.customElement = options.customElement;
this.css_mode = options.css;

this.html = {
start: null,
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/parse/read/style.ts
Expand Up @@ -19,6 +19,12 @@ export default function read_style(parser: Parser, start: number, attributes: No

const content_end = parser.index;

// discard styles when css is disabled
if (parser.css_mode === 'none') {
parser.read(regex_closing_style_tag);
return null;
}

let ast;

try {
Expand Down Expand Up @@ -71,6 +77,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
});

parser.read(regex_closing_style_tag);

const end = parser.index;

return {
Expand Down
7 changes: 7 additions & 0 deletions test/parser/samples/css-option-none/input.svelte
@@ -0,0 +1,7 @@
<div>foo</div>

<style>
div {
color: red;
}
</style>
3 changes: 3 additions & 0 deletions test/parser/samples/css-option-none/options.json
@@ -0,0 +1,3 @@
{
"css": "none"
}
32 changes: 32 additions & 0 deletions test/parser/samples/css-option-none/output.json
@@ -0,0 +1,32 @@
{
"html": {
"start": 0,
"end": 14,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 14,
"type": "Element",
"name": "div",
"attributes": [],
"children": [
{
"start": 5,
"end": 8,
"type": "Text",
"raw": "foo",
"data": "foo"
}
]
},
{
"start": 14,
"end": 16,
"type": "Text",
"raw": "\n\n",
"data": "\n\n"
}
]
}
}