svelte-preprocess-cssmodules
Advanced tools
Comparing version 2.1.3 to 2.2.0
@@ -1,5 +0,6 @@ | ||
import type { PluginOptions, PreprocessorOptions, PreprocessorResult } from './types'; | ||
declare const _default: (options: Partial<PluginOptions>) => { | ||
markup: ({ content, filename }: PreprocessorOptions) => Promise<PreprocessorResult>; | ||
}; | ||
import type { PreprocessorGroup } from 'svelte/types/compiler/preprocess/index.d'; | ||
import type { PluginOptions } from './types'; | ||
export declare const cssModules: (options: Partial<PluginOptions>) => PreprocessorGroup; | ||
export declare const linearPreprocess: (preprocessors: PreprocessorGroup[]) => PreprocessorGroup[]; | ||
declare const _default: (options: Partial<PluginOptions>) => PreprocessorGroup; | ||
export default _default; |
@@ -12,2 +12,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.linearPreprocess = exports.cssModules = void 0; | ||
const compiler_1 = require("svelte/compiler"); | ||
@@ -18,2 +19,3 @@ const processors_1 = require("./processors"); | ||
return { | ||
cssVariableHash: '[hash:base64:6]', | ||
getLocalIdent: lib_1.getLocalIdent, | ||
@@ -36,3 +38,9 @@ hashSeeder: ['style', 'filepath', 'classname'], | ||
} | ||
const ast = compiler_1.parse(content, { filename }); | ||
let ast; | ||
try { | ||
ast = compiler_1.parse(content, { filename }); | ||
} | ||
catch (err) { | ||
throw new Error(`${err}\n\nThe svelte component failed to be parsed. Make sure cssModules is running after all other preprocessors by wrapping them with "cssModulesPreprocess().after()"`); | ||
} | ||
if (!pluginOptions.useAsDefaultScoping && | ||
@@ -75,3 +83,3 @@ !lib_1.hasModuleAttribute(ast) && | ||
}); | ||
exports.default = module.exports = (options) => { | ||
exports.cssModules = (options) => { | ||
pluginOptions = Object.assign(Object.assign({}, defaultOptions()), options); | ||
@@ -85,1 +93,15 @@ if (pluginOptions.includePaths) { | ||
}; | ||
exports.linearPreprocess = (preprocessors) => { | ||
return preprocessors.map((p) => { | ||
return !p.script && !p.style | ||
? p | ||
: { | ||
markup({ content, filename }) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
return compiler_1.preprocess(content, p, { filename }); | ||
}); | ||
}, | ||
}; | ||
}); | ||
}; | ||
exports.default = module.exports = exports.cssModules; |
export { default as camelCase } from './camelCase'; | ||
export { default as createClassName } from './createClassName'; | ||
export * from './generateName'; | ||
export * from './getLocalIdent'; | ||
export * from './requirement'; |
@@ -16,8 +16,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createClassName = exports.camelCase = void 0; | ||
exports.camelCase = void 0; | ||
var camelCase_1 = require("./camelCase"); | ||
Object.defineProperty(exports, "camelCase", { enumerable: true, get: function () { return __importDefault(camelCase_1).default; } }); | ||
var createClassName_1 = require("./createClassName"); | ||
Object.defineProperty(exports, "createClassName", { enumerable: true, get: function () { return __importDefault(createClassName_1).default; } }); | ||
__exportStar(require("./generateName"), exports); | ||
__exportStar(require("./getLocalIdent"), exports); | ||
__exportStar(require("./requirement"), exports); |
@@ -25,5 +25,43 @@ "use strict"; | ||
}; | ||
const addDynamicVariablesToElements = (processor, node, cssVar) => { | ||
var _a; | ||
(_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach((childNode) => { | ||
if (childNode.type === 'InlineComponent') { | ||
addDynamicVariablesToElements(processor, childNode, cssVar); | ||
} | ||
else if (childNode.type === 'Element') { | ||
const attributesLength = childNode.attributes.length; | ||
if (attributesLength) { | ||
const styleAttr = childNode.attributes.find((attr) => attr.name === 'style'); | ||
if (styleAttr) { | ||
processor.magicContent.appendLeft(styleAttr.value[0].start, cssVar.values); | ||
} | ||
else { | ||
const lastAttr = childNode.attributes[attributesLength - 1]; | ||
processor.magicContent.appendRight(lastAttr.end, ` ${cssVar.styleAttribute}`); | ||
} | ||
} | ||
else { | ||
processor.magicContent.appendRight(childNode.start + childNode.name.length + 1, ` ${cssVar.styleAttribute}`); | ||
} | ||
} | ||
}); | ||
}; | ||
const cssVariables = (processor) => { | ||
const cssVarListKeys = Object.keys(processor.cssVarList); | ||
let styleAttribute = ''; | ||
let values = ''; | ||
if (cssVarListKeys.length) { | ||
for (let i = 0; i < cssVarListKeys.length; i += 1) { | ||
const key = cssVarListKeys[i]; | ||
values += `--${processor.cssVarList[key]}:{${key}};`; | ||
} | ||
styleAttribute = `style="${values}"`; | ||
} | ||
return { styleAttribute, values }; | ||
}; | ||
exports.default = (processor) => { | ||
const directiveLength = 'class:'.length; | ||
const allowedAttributes = ['class', ...processor.options.includeAttributes]; | ||
const cssVar = cssVariables(processor); | ||
compiler_1.walk(processor.ast.html, { | ||
@@ -35,2 +73,5 @@ enter(baseNode) { | ||
} | ||
if (node.type === 'Fragment' && cssVar.values.length) { | ||
addDynamicVariablesToElements(processor, node, cssVar); | ||
} | ||
if (['Element', 'InlineComponent'].includes(node.type) && node.attributes.length > 0) { | ||
@@ -41,3 +82,3 @@ node.attributes.forEach((item) => { | ||
var _a; | ||
if (classItem.type === 'Text') { | ||
if (classItem.type === 'Text' && classItem.data.length > 0) { | ||
const generatedClassNames = updateMultipleClasses(processor, classItem.data); | ||
@@ -44,0 +85,0 @@ processor.magicContent.overwrite(classItem.start, classItem.start + classItem.data.length, generatedClassNames); |
@@ -79,2 +79,3 @@ "use strict"; | ||
} | ||
processor.parseBoundVariables(node); | ||
if (node.type === 'ClassSelector') { | ||
@@ -81,0 +82,0 @@ const generatedClassName = processor.createModuleClassname(node.name); |
@@ -66,2 +66,3 @@ "use strict"; | ||
} | ||
processor.parseBoundVariables(node); | ||
if (node.type === 'ClassSelector') { | ||
@@ -68,0 +69,0 @@ const generatedClassName = processor.createModuleClassname(node.name); |
@@ -9,2 +9,3 @@ import MagicString from 'magic-string'; | ||
cssModuleList: CSSModuleList; | ||
cssVarList: CSSModuleList; | ||
importedCssModuleList: CSSModuleList; | ||
@@ -23,4 +24,5 @@ ast: Ast; | ||
addModule: (name: string, value: string) => void; | ||
parseBoundVariables: (node: TemplateNode) => void; | ||
parsePseudoLocalSelectors: (node: TemplateNode) => void; | ||
parse: () => string; | ||
} |
@@ -12,2 +12,3 @@ "use strict"; | ||
this.cssModuleList = {}; | ||
this.cssVarList = {}; | ||
this.importedCssModuleList = {}; | ||
@@ -25,2 +26,22 @@ this.isParsingImports = false; | ||
}; | ||
this.parseBoundVariables = (node) => { | ||
var _a, _b; | ||
const bindedVariableNodes = (_b = (_a = node.children) === null || _a === void 0 ? void 0 : _a.filter((item) => { var _a; return item.type === 'Function' && item.name === 'bind' && ((_a = node.children) === null || _a === void 0 ? void 0 : _a.length); })) !== null && _b !== void 0 ? _b : []; | ||
if (bindedVariableNodes.length > 0) { | ||
bindedVariableNodes.forEach((item) => { | ||
var _a; | ||
if (item.children) { | ||
const child = item.children[0]; | ||
const name = (_a = child.name) !== null && _a !== void 0 ? _a : child.value.replace(/'|"/g, ''); | ||
const varName = child.type === 'String' ? name.replace(/\./, '-') : name; | ||
const generatedVarName = lib_1.generateName(this.filename, this.ast.css.content.styles, varName, { | ||
hashSeeder: ['style', 'filepath'], | ||
localIdentName: `[local]-${this.options.cssVariableHash}`, | ||
}); | ||
this.magicContent.overwrite(item.start, item.end, `var(--${generatedVarName})`); | ||
this.cssVarList[name] = generatedVarName; | ||
} | ||
}); | ||
} | ||
}; | ||
this.parsePseudoLocalSelectors = (node) => { | ||
@@ -46,3 +67,3 @@ var _a, _b; | ||
} | ||
if (Object.keys(this.cssModuleList).length > 0) { | ||
if (Object.keys(this.cssModuleList).length > 0 || Object.keys(this.cssVarList).length > 0) { | ||
parsers_1.parseTemplate(this); | ||
@@ -49,0 +70,0 @@ } |
@@ -25,2 +25,3 @@ "use strict"; | ||
} | ||
processor.parseBoundVariables(node); | ||
if (node.type === 'ClassSelector') { | ||
@@ -27,0 +28,0 @@ const generatedClassName = processor.createModuleClassname(node.name); |
import type { GetLocalIdent } from '../lib'; | ||
export declare type PluginOptions = { | ||
cssVariableHash: string; | ||
getLocalIdent: GetLocalIdent; | ||
@@ -13,10 +14,3 @@ hashSeeder: Array<'style' | 'filepath' | 'classname'>; | ||
}; | ||
export interface PreprocessorOptions { | ||
content: string; | ||
filename: string; | ||
} | ||
export interface PreprocessorResult { | ||
code: string; | ||
} | ||
export declare type CSSModuleList = Record<string, string>; | ||
export declare type CSSModuleDirectory = Record<string, CSSModuleList>; |
{ | ||
"name": "svelte-preprocess-cssmodules", | ||
"version": "2.1.3", | ||
"version": "2.2.0", | ||
"description": "Svelte preprocessor to generate CSS Modules classname on Svelte components", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
520
README.md
# Svelte preprocess CSS Modules | ||
Generate CSS Modules classname on Svelte components | ||
Generate CSS Modules classnames on Svelte components | ||
@@ -9,6 +9,10 @@ ```bash | ||
## Table of Content | ||
- [Usage](#usage) | ||
- [Modes](#modes) | ||
- [Approach](#approach) | ||
- [Class directive](#class-directive) | ||
- [Local selector](#local-selector) | ||
- [Local selector](#local-selector) | ||
- [CSS binding](#css-binding) | ||
- [Scoped class on child components](#scoped-class-on-child-components) | ||
- [Import styles from an external stylesheet](#import-styles-from-an-external-stylesheet) | ||
@@ -19,2 +23,8 @@ - [Destructuring import](#destructuring-import) | ||
- [Directive and dynamic class](#directive-and-dynamic-class) | ||
- [Preprocessor Modes](#preprocessor-modes) | ||
- [Native](#native) | ||
- [Mixed](#mixed) | ||
- [Scoped](#scoped) | ||
- [Comparative](#comparative) | ||
- [Why CSS Modules over Svelte scoping?](#why-css-modules-over-svelte-scoping) | ||
- [Configuration](#configuration) | ||
@@ -28,11 +38,9 @@ - [Rollup](#rollup) | ||
- [Code example](#code-example) | ||
- [Why CSS Modules on Svelte](#why-css-modules-on-svelte) | ||
## Usage | ||
Add the `module` attribute to the `<style>` tag to enable cssModules | ||
Add the `module` attribute to `<style>` | ||
```html | ||
<style module> | ||
p { font-size: 14px; } | ||
.red { color: red; } | ||
@@ -44,7 +52,6 @@ </style> | ||
The component will be transformed to | ||
The component will be compiled to | ||
```html | ||
<style> | ||
p { font-size: 14px; } | ||
.red-30_1IC { color: red; } | ||
@@ -56,52 +63,51 @@ </style> | ||
### Modes | ||
### Approach | ||
Preprocessor can operate in the following modes: | ||
The default svelte scoping appends every css selectors with a unique class to only affect the elements of the component. | ||
- `native` (default) - scopes classes with cssModules, anything else is unscoped | ||
- `mixed` - scopes non-class selectors with svelte scoping in addition to `native` (same as preprocessor `v1`) | ||
- `scoped` - scopes classes with svelte scoping in addition to `mixed` | ||
[CSS Modules](https://github.com/css-modules/css-modules) **scopes each class name** with a unique id/name in order to affect the elements of the component. As the other selectors are not scoped, it is recommended to write each selector with a class. | ||
The mode can be **set globally from the preprocessor options** or **locally to override the global settings** per component. | ||
```html | ||
<!-- Component A --> | ||
<p>lorem ipsum tut moue</p> | ||
<p class="red">lorem ipsum tut moue</p> | ||
**Mixed mode** | ||
```html | ||
<style module="mixed"> | ||
<style module> | ||
p { font-size: 14px; } | ||
.red { color: red; } | ||
</style> | ||
``` | ||
```html | ||
<!-- Component B --> | ||
<p class="text">lorem ipsum tut moue</p> | ||
<p class="text red">lorem ipsum tut moue</p> | ||
<p class="red">My red text</p> | ||
<style module> | ||
.text { font-size: 14px; } | ||
.red { color: red; } | ||
</style> | ||
``` | ||
*generating* | ||
_transformed to_ | ||
```html | ||
<style> | ||
p.svelte-teyu13r { font-size: 14px; } | ||
.red-30_1IC { color: red; } | ||
<!-- Component A --> | ||
<p>lorem ipsum tut moue</p> | ||
<p class="red-123qwe">lorem ipsum tut moue</p> | ||
<style module> | ||
p { font-size: 14px; } /* global rule */ | ||
.red-123qwe { color: red; } | ||
</style> | ||
<p class="red-30_1IC svelte-teyu13r">My red text</p> | ||
``` | ||
**Scoped mode** | ||
```html | ||
<style module="scoped"> | ||
p { font-size: 14px; } | ||
.red { color: red; } | ||
</style> | ||
<!-- Component B --> | ||
<p class="text-456rty">lorem ipsum tut moue</p> | ||
<p class="text-456rty red-123qwe">lorem ipsum tut moue</p> | ||
<p class="red">My red text</p> | ||
``` | ||
*generating* | ||
```html | ||
<style> | ||
p.svelte-teyu13r { font-size: 14px; } | ||
.red-30_1IC.svelte-teyu13r { color: red; } | ||
<style> /* all scoped to component */ | ||
.text-456rty { font-size: 14px; } | ||
.red-123qwe { color: red; } | ||
</style> | ||
<p class="red-30_1IC svelte-teyu13r">My red text</p> | ||
``` | ||
@@ -115,29 +121,31 @@ | ||
<script> | ||
let isActive = true; | ||
let route = 'home'; | ||
$: isActive = route === 'home'; | ||
</script> | ||
<style module> | ||
.bold { font-weight: bold; } | ||
.active { font-weight: bold; } | ||
</style> | ||
<p class:bold={isActive}>My red text</p> | ||
<p class="{isActive ? 'bold' : ''}">My blue text</p> | ||
<a class:active={isActive} href="/">Home</a> | ||
<!-- or --> | ||
<a class="{isActive ? 'active' : ''}" href="/">Home</a> | ||
``` | ||
*Generating* | ||
_generating_ | ||
```html | ||
<style> | ||
.bold-2jIMhI { font-weight: bold; } | ||
.active-2jIMhI { font-weight: bold; } | ||
</style> | ||
<p class="bold-2jIMhI">My red text</p> | ||
<p class="bold-2jIMhI">My blue text</p> | ||
<a class="active-2jIMhI" href="/">Home</a> | ||
``` | ||
**Use of shorthand** | ||
#### Use of shorthand | ||
```html | ||
<script> | ||
let active = true; | ||
let route = 'home'; | ||
$: active = route === 'home'; | ||
</script> | ||
@@ -149,6 +157,6 @@ | ||
<p class:active>My active text</p> | ||
<a class:active href="/">Home</a> | ||
``` | ||
*Generating* | ||
_generating_ | ||
@@ -160,13 +168,13 @@ ```html | ||
<p class="active-2jIMhI">My active text</p> | ||
<a class="active-2jIMhI" href="/">Home</a> | ||
``` | ||
## Local selector | ||
### Local selector | ||
Force a selector to be scoped within a component to prevent style inheritance on child components. | ||
Force a selector to be scoped within its component to prevent style inheritance on child components. | ||
`:local()` is doing the opposite of `:global()` and can only be used with the `native` and `mixed` mode on. | ||
`:local()` is doing the opposite of `:global()` and can only be used with the `native` and `mixed` modes ([see preprocessor modes](#preprocessor-modes)). The svelte scoping is applied to the selector inside `:local()`. | ||
```html | ||
<!-- Parent Component--> | ||
<!-- Parent Component --> | ||
@@ -191,3 +199,3 @@ <style module> | ||
/** | ||
* Not needed rule because of the use of :local() | ||
* Unnecessary rule because of the use of :local() | ||
.child strong { font-weight: 700 } | ||
@@ -200,3 +208,3 @@ */ | ||
*Generating* | ||
*generating* | ||
@@ -226,3 +234,3 @@ ```html | ||
When used on a class, `:local()` applies the svelte scoping system to the selector. This could be useful when targetting global classnames. | ||
When used with a class, `:local()` cssModules is replaced by the svelte scoping system. This could be useful when targetting global classnames. | ||
@@ -246,3 +254,3 @@ ```html | ||
*Generating* | ||
*generating* | ||
@@ -265,11 +273,174 @@ ```html | ||
### CSS binding | ||
Link the value of a CSS property to a dynamic variable by using `bind()`. | ||
```html | ||
<script> | ||
let color = 'red'; | ||
</script> | ||
<p class="text">My lorem ipsum text</p> | ||
<style module> | ||
.text { | ||
font-size: 18px; | ||
font-weight: bold; | ||
color: bind(color); | ||
} | ||
</style> | ||
``` | ||
A scoped css variable, binding the declared statement, will be created on the component **root** elements which the css property will inherit from. | ||
```html | ||
<script> | ||
let color = 'red'; | ||
</script> | ||
<p class="text-t56rwy" style="--color-eh7sp:{color}"> | ||
My lorem ipsum text | ||
</p> | ||
<style> | ||
.text-t56rwy { | ||
font-size: 18px; | ||
font-weight: bold; | ||
color: var(--color-eh7sp); | ||
} | ||
</style> | ||
``` | ||
An object property can also be targetted and must be wrapped with quotes. | ||
```html | ||
<script> | ||
const style = { | ||
opacity: 0; | ||
}; | ||
</script> | ||
<div class="content"> | ||
<h1>Heading</h1> | ||
<p class="text">My lorem ipsum text</p> | ||
</div> | ||
<style module> | ||
.content { | ||
padding: 10px 20px; | ||
background-color: #fff; | ||
} | ||
.text { | ||
opacity: bind('style.opacity'); | ||
} | ||
</style> | ||
``` | ||
_generating_ | ||
```html | ||
<script> | ||
const style = { | ||
opacity: 0; | ||
}; | ||
</script> | ||
<div class="content-dhye8T" style="--opacity-r1gf51:{style.opacity}"> | ||
<h1>Heading</h1> | ||
<p class="text-iTsx5A">My lorem ipsum text</p> | ||
</div> | ||
<style> | ||
.content-dhye8T { | ||
padding: 10px 20px; | ||
background-color: #fff; | ||
} | ||
.text-iTsx5A { | ||
opacity: var(--opacity-r1gf51); | ||
} | ||
</style> | ||
``` | ||
### Scoped class on child components | ||
CSS Modules allows you to pass a scoped classname to a child component giving the possibility to style it from its parent. (Only with the `native` and `mixed` modes – [See preprocessor modes](#preprocessor-modes)). | ||
```html | ||
<!-- Child Component Button.svelte --> | ||
<script> | ||
let className; | ||
export { className as class }; | ||
</script> | ||
<button class="btn {className}"> | ||
<slot /> | ||
</button> | ||
<style module> | ||
.btn { | ||
background: red; | ||
color: white; | ||
} | ||
</style> | ||
``` | ||
```html | ||
<!-- Parent Component --> | ||
<script> | ||
import Button from './Button.svelte'; | ||
</script> | ||
<div class="wrapper"> | ||
<h1>Welcome</h1> | ||
<p>Lorem ipsum tut ewou tu po</p> | ||
<Button class="btn">Start</Button> | ||
</div> | ||
<style module> | ||
.wrapper { | ||
margin: 0 auto; | ||
padding: 16px; | ||
max-width: 400px; | ||
} | ||
.btn { | ||
margin-top: 30px; | ||
} | ||
</style> | ||
``` | ||
_generating_ | ||
```html | ||
<div class="wrapper-tyaW3"> | ||
<h1>Welcome</h1> | ||
<p>Lorem ipsum tut ewou tu po</p> | ||
<button class="btn-dtg87W btn-rtY6ad">Start</button> | ||
</div> | ||
<style> | ||
.wrapper-tyaW3 { | ||
margin: 0 auto; | ||
padding: 16px; | ||
max-width: 400px; | ||
} | ||
.btn-dtg87W { | ||
margin-top: 30px; | ||
} | ||
.btn-rtY6ad { | ||
background: red; | ||
color: white; | ||
} | ||
</style> | ||
``` | ||
## Import styles from an external stylesheet | ||
Alternatively, styles can be created into an external file and imported onto a svelte component from the `<script>` tag. The name referring to the import can then be used in the markup targetting any existing classname of the stylesheet. | ||
Alternatively, styles can be created into an external file and imported onto a svelte component. The name referring to the import can then be used on the markup to target any existing classname of the stylesheet. | ||
- The option `parseExternalStylesheet` need to be enabled. | ||
- The css file must follow the convention `FILENAME.module.css` in order to be processed. | ||
- The css file must follow the convention `[FILENAME].module.css` in order to be processed. | ||
**Note:** *The import option is only meant for stylesheets relative to the component. You will have to set your own bundler in order to import *node_modules* packages css files.* | ||
**Note:** *That import is only meant for stylesheets relative to the component. You will have to set your own bundler in order to import *node_modules* css files.* | ||
@@ -291,3 +462,3 @@ ```css | ||
*Generated code* | ||
*generating* | ||
@@ -325,3 +496,3 @@ ```html | ||
*Generated code* | ||
*generating* | ||
@@ -344,3 +515,3 @@ ```html | ||
The kebab-case classnames are being transformed to a camelCase version on imports to facilitate their use on Markup and Javascript. | ||
The kebab-case class names are being transformed to a camelCase version to facilitate their use on Markup and Javascript. | ||
@@ -373,3 +544,3 @@ ```css | ||
*Generated code* | ||
*generating* | ||
@@ -391,3 +562,3 @@ ```html | ||
If a css file is being imported without a name, the cssModules will still be applied to the classes of the stylesheet. | ||
If a css file is being imported without a name, CSS Modules will still apply to the classes of the stylesheet. | ||
@@ -408,3 +579,3 @@ ```css | ||
*Generated code* | ||
*generating* | ||
@@ -448,2 +619,142 @@ ```html | ||
## Preprocessor Modes | ||
The mode can be **set globally from the config** or **locally to override the global setting**. | ||
### Native | ||
Scopes classes with CSS Modules, anything else is unscoped. | ||
Pros: | ||
- uses default [CSS Modules](https://github.com/css-modules/css-modules) approach | ||
- creates unique ID to avoid classname conflicts and unexpected inheritances | ||
- passes scoped class name to child components | ||
Cons: | ||
- does not scope non class selectors. | ||
- forces to write selectors with classes. | ||
- needs to consider third party plugins with `useAsDefaultScoping` on – [Read more](#useasdefaultscoping). | ||
### Mixed | ||
Scopes non-class selectors with svelte scoping in addition to `native` (same as preprocessor `v1`) | ||
```html | ||
<style module="mixed"> | ||
p { font-size: 14px; } | ||
.red { color: red; } | ||
</style> | ||
<p class="red">My red text</p> | ||
``` | ||
_generating_ | ||
```html | ||
<style> | ||
p.svelte-teyu13r { font-size: 14px; } | ||
.red-30_1IC { color: red; } | ||
</style> | ||
<p class="red-30_1IC svelte-teyu13r">My red text</p> | ||
``` | ||
Pros: | ||
- creates class names with unique ID to avoid conflicts and unexpected inheritances | ||
- uses svelte scoping on non class selectors | ||
- passes scoped class name to child components | ||
Cons: | ||
- adds more weight to tag selectors than class selectors (because of the svelte scoping) | ||
```html | ||
<ul> | ||
<li>Home</li> | ||
<li class="active">About</li> | ||
</ul> | ||
<style module="mixed"> | ||
li { | ||
color: gray; | ||
} | ||
/* this will never be applied */ | ||
.active { | ||
color: blue; | ||
} | ||
/* forcing us to write that instead */ | ||
li.active { | ||
color: blue; | ||
} | ||
</style> | ||
<!-- or rewriting the component --> | ||
<ul> | ||
<li class="item">Home</li> | ||
<li class="item active">About</li> | ||
</ul> | ||
<style module="mixed"> | ||
.item { | ||
color: gray; | ||
} | ||
.active { | ||
color: blue; | ||
} | ||
</style> | ||
``` | ||
### Scoped | ||
Scopes classes with svelte scoping in addition to `mixed`. | ||
```html | ||
<style module="scoped"> | ||
p { font-size: 14px; } | ||
.red { color: red; } | ||
</style> | ||
<p class="red">My red text</p> | ||
``` | ||
_generating_ | ||
```html | ||
<style> | ||
p.svelte-teyu13r { font-size: 14px; } | ||
.red-30_1IC.svelte-teyu13r { color: red; } | ||
</style> | ||
<p class="red-30_1IC svelte-teyu13r">My red text</p> | ||
``` | ||
Pros: | ||
- creates class names with unique ID to avoid conflicts and unexpected inheritances | ||
- scopes every selectors at equal weight | ||
Cons: | ||
- does not pass scoped classname to child components | ||
### Comparative | ||
| | Svelte scoping | Preprocessor Native | Preprocessor Mixed | Preprocessor Scoped | | ||
| -------------| ------------- | ------------- | ------------- | ------------- | | ||
| Scopes classes | O | O | O | O | | ||
| Scopes non class selectors | O | X | O | O | | ||
| Creates unique class ID | X | O | O | O | | ||
| Has equal selector weight | O | O | X | O | | ||
| Passes scoped classname to a child component | X | O | O | X | | ||
## Why CSS Modules over Svelte scoping? | ||
- **On a full svelte application**: it is just a question of taste as the default svelte scoping is largely enough. Component styles will never inherit from other styling. | ||
- **On a hybrid project** (like using svelte to enhance a web page): the default scoping may actually inherits from a class of the same name belonging to the style of the page. In that case using CSS Modules to create a unique ID and to avoid class inheritance might be advantageous. | ||
## Configuration | ||
@@ -525,28 +836,33 @@ | ||
Chaining several preprocessors may lead to errors if the svelte parser and walker is being manipulated multiple time. This issue is due to the way svelte runs its preprocessor in two phases. [Read more here](https://github.com/firefish5000/svelte-as-markup-preprocessor#motivation) | ||
Svelte is running the preprocessors by phases, going through all *markup* first, followed by *script* and then *style*. | ||
The CSS Modules preprocessor is doing all its work on the markup phase via `svelte.parse()` which requires the compoment to be a valid standard svelte component (using vanilla js and vanilla css). if any other code (such as typescript or sass) is encountered, an error will be thrown. | ||
In that situation, we recommend the use of the package [`svelte-as-markup-preprocessor`](https://github.com/firefish5000/svelte-as-markup-preprocessor). | ||
```js | ||
const { typescript, scss } = require('svelte-preprocess'); | ||
const { cssModules } = require('svelte-preprocess-cssmodules'); | ||
```bash | ||
npm install --save-dev svelte-as-markup-preprocessor | ||
... | ||
// svelte config: NOT working! | ||
preprocess: [ | ||
typescript(), // 2 run second on script phase | ||
scss(), // 3 run last on style phase | ||
cssModules(), // 1 run first on markup phase | ||
], | ||
... | ||
``` | ||
**Example with typescript** | ||
As it is extremely common for developers to use `svelte-preprocess` in their application, CSS Modules provides a small utility to easily be incorporated with. `linearPreprocess` will ensure a linear process with the list of preprocessors. | ||
```js | ||
// import packages | ||
const { typescript } = require('svelte-preprocess'); | ||
const { asMarkupPreprocessor } = require('svelte-as-markup-preprocessor'); | ||
const cssModules = require('svelte-preprocess-cssmodules'); | ||
const { typescript, scss } = require('svelte-preprocess'); | ||
const { cssModules, linearPreprocess } = require('svelte-preprocess-cssmodules'); | ||
... | ||
// svelte config | ||
preprocess: [ | ||
asMarkupPreprocessor([ | ||
typescript() | ||
]), | ||
cssModules() | ||
], | ||
// svelte config: OK, processing one after another! | ||
preprocess: linearPreprocess([ | ||
typescript(), // 1 run first | ||
scss(), // 2 run second | ||
cssModules(), // 3 run last | ||
]), | ||
... | ||
@@ -560,2 +876,3 @@ ``` | ||
| ------------- | ------------- | ------------- | ------------- | | ||
| `cssVariableHash` | `{String}` | `[hash:base64:6]` | The hash type ([see locatonIdentName](#localidentname)) | | ||
| [`getLocalIdent`](#getlocalident) | `Function` | `undefined` | Generate the classname by specifying a function instead of using the built-in interpolation | | ||
@@ -632,3 +949,3 @@ | [`hashSeeder`](#hashseeder) | `{Array}` | `['style', 'filepath', 'classname']` | An array of keys to base the hash on | | ||
Set the source to create the hash from (when using `[hash]` / `[contenthash]`). | ||
Set the source of the hash (when using `[hash]` / `[contenthash]`). | ||
@@ -639,5 +956,5 @@ The list of available keys are: | ||
- `filepath` the path of the component | ||
- `classname` the local className | ||
- `classname` the local classname | ||
*Example of use* | ||
*Example of use: creating a common hash per component* | ||
```js | ||
@@ -648,3 +965,3 @@ // Preprocess config | ||
cssModules({ | ||
hashSeeder: ['filepath'], | ||
hashSeeder: ['filepath', 'style'], | ||
}) | ||
@@ -663,3 +980,3 @@ ], | ||
*Generating* | ||
_generating_ | ||
@@ -698,3 +1015,3 @@ ```html | ||
*Generating* | ||
_generating_ | ||
@@ -727,3 +1044,3 @@ ```html | ||
Globally replace the default svelte scoping by the cssModules scoping. As a result, the `module` attribute to `<style>` becomes unnecessary. | ||
Globally replace the default svelte scoping by the CSS Modules scoping. As a result, the `module` attribute to `<style>` becomes unnecessary. | ||
@@ -749,3 +1066,4 @@ | ||
*Generating* | ||
_generating_ | ||
```html | ||
@@ -758,7 +1076,8 @@ <h1 class="title-erYt1">Welcome</h1> | ||
**Beware** | ||
The enabled option will applied cssModules scoping to all imported Svelte files, even the ones coming from `node_modules`. When using a third party library, make sure the compiled version is being imported. In the case of a raw svelte file, it might break its styling. | ||
**Potential issue with third party plugins** | ||
To prevent any scoping conflict, it is recommended to associate the option `useAsDefaultScoping` with `includePaths`. | ||
The preprocessor requires you to add the `module` attribute to `<style>` in order to apply CSS Modules to a component. When enabling `useAsDefaultScoping` the `module` attribute is not required anymore and CSS Modules will apply to all svelte components of your application, including plugins. If a third party component is relying on svelte scoping and non class selectors, its styling will apply globally and may cause unexpected results. | ||
To prevent any scoping conflict, it is recommended to set the `includePaths` option to your source folder in order to target your components only. | ||
```js | ||
@@ -884,9 +1203,4 @@ // Preprocess config | ||
``` | ||
## Why CSS Modules on Svelte | ||
While the native CSS Scoped system should be largely enough to avoid class conflict, it could find its limit when working on a hybrid project. On a non full Svelte application, paying attention to the name of a class would be no less different than to a regular html project. For example, on the modal component above, It would have been wiser to namespace some of the classes such as `.modal-body` and `.modal-cancel` in order to prevent inheriting styles from other `.body` and `.cancel` classes. | ||
## License | ||
[MIT](https://opensource.org/licenses/MIT) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
102593
1598
1176