/* 基本 Block */
.button {
background-color: #4CAF50;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
}
/* Element */
.button__icon {
/* 添加 icon 的样式 */
}
/* Modifier */
.button--primary {
background-color: #008CBA;
}
.button--danger {
background-color: #f44336;
}
// 通过 css-loader 转换 hash 类名
const path = require('path');
const { getLocalIdent } = require('./css-modules');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.css$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
module: true,
getLocalIdent: (loaderContext, localIdentName, localName, options) => {
return getLocalIdent(loaderContext, localIdentName, localName, options);
},
},
},
],
},
],
},
};
// 将类名转换成唯一 hash
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
function generateHashedClassName(filename, className) {
const content = fs.readFileSync(filename, 'utf-8');
const hash = crypto
.createHash('md5')
.update(content)
.digest('base64')
.substr(0, 5);
return `${className}--${hash}`;
}
function getLocalIdent(loaderContext, localIdentName, localName, options) {
const fileName = path.basename(loaderContext.resourcePath);
return generateHashedClassName(loaderContext.resourcePath, localName);
}
module.exports = {
getLocalIdent
};
详情
核心是将唯一样式和元素关联起来
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS-in-JS</title>
</head>
<body>
<button id="myButton">Click me!</button>
<script src="css-in-js.js"></script>
</body>
</html>
const myButton = document.getElementById('myButton');
// 生成唯一哈希值
function generateHash() {
return Math.random().toString(36).substr(2, 5);
}
// 创建样式标签
function createStyleTag(css) {
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}
// 将样式与元素关联
function cssInJs(element, styles) {
const hashedClassName = 'css_in_js_' + generateHash();
element.classList.add(hashedClassName);
const css = `
.${hashedClassName} {
background-color: lightblue;
color: white;
padding: 0.5rem 1rem;
border: none;
font-size: 1.2rem;
cursor: pointer;
}
`;
createStyleTag(css);
}
// 应用样式
cssInJs(myButton);
const myButton = document.getElementById('myButton');
const styleElement = document.getElementById('css');
// 生成唯一哈希值
function generateHash() {
return Math.random().toString(36).substr(2, 5);
}
// 添加唯一属性
function addScopedAttribute(element, attributeName) {
element.setAttribute(attributeName, '');
}
// 创建 Scoped CSS 的样式
function createScopedCSS(attributeName, css) {
// 将应用到组件的原始选择器类名替换成属性选择器
// .myButton {...} 类样式将变为 .myButton[data-scoped-abcdef] {...}
const scopedCSS = css.replace(/(\.\w+)/g, `$1[${attributeName}]`);
styleElement.textContent += scopedCSS;
}
// 实现 Scoped CSS
function scopedCSS(element, styles) {
const uniqueAttribute = `data-scoped-${generateHash()}`;
// 将唯一属性设置到元素上,从而能通过属性选择器匹配
addScopedAttribute(element, uniqueAttribute);
createScopedCSS(uniqueAttribute, styles);
}
// 定义样式
const buttonStyles = `
.myButton {
background-color: lightblue;
color: white;
padding: 0.5rem 1rem;
border: none;
font-size: 1.2rem;
cursor: pointer;
}
`;
// 应用样式
scopedCSS(myButton, buttonStyles);