作者:道乐技术
回顾一期内容,本期我们重点来说一下微前端中的CSS隔离知识点
道乐技术部,公众号:道乐技术【web前端技术】微前端-第一期
CSS隔离
在计算机科学中只有两件事情最难:缓存失效和取名字。
—— Phil Karlton
当我们进行某一模块的布局时,首先就是要想出一个好名字,而且还不能和其他模块冲突。这对于词汇量少的同学来说可谓是一种折磨。所以我们可以看到某些老项目里会存在着像 a-fund、b-fund的类似命名方式,简直就是大千世界,无奇不有。
这个时候,请大声喊出我们最大的愿望:赐予我姓名吧!把这些样式隔离起来吧!
那我们要如何进行隔离,防止样式冲突污染呢?
1、web component 之下的 Shadow DOM
可以说组件化和复用,几乎是所有开发者所追求的。Web Components 就这样在人们的期盼下诞生了。它通过一种标准化的、非侵入的方式来封装一个组件,每个组件能组织好它自身的 HTML 结构、CSS 样式、JavaScript 代码,并且不会干扰页面上的其他代码,甚至这些组件可以无缝接入到框架中。
只需要创造一个定制的HTML标签,就会继承HTML元素的所有属性,并且可以在任何支持的浏览器中通过简单地引入一个Script,让所有的HTML、CSS、JavaScript在组件内部局部定义。这个组件在你的浏览器开发工具中显示为一个单独的HTML标签,而且它的样式和行为都是完全在组件内进行的,不需要进行工作区、框架和一些前置的转换。
Web Components 主要由四大规范组合而成:Custom elements、Shadow DOM、HTML templates、HTML Imports。少了任何一个,它都缺少了灵魂。
这次我们来重点介绍——Shadow DOM:
ShadowDOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素。自定义元素的HTML和CSS完全封装在组件内。这意味着元素将以单个的HTML标签出现在文档的DOM树中,其内部的结构将会放在#shadow-root。
实际上一些原生的HTML元素也使用了Shadow DOM。例如video:
可能有些同学打开浏览器控制台会发现上图中的#shadow-root并没有出现,这时你只需要稍微改下配置就可以了,因为在浏览器中,这是默认被隐藏的。
下面我们给大家演示一个简单的样式隔离:
<div>
<div id="video"></div>
<h1>三国演义电影</h1>
</div>
<template id="tpl">
<style>
h1 {
color: #E85E5E;
}
</style>
<h1>三国演义电影</h1>
</template>
<script>
const root = document.querySelector('#video').attachShadow({mode:'open'})
root.appendChild(document.querySelector('#tpl').content)
</script>
我们可以发现,我们在Shadow DOM里面定义的h1 样式是局部作用域的CSS。所有的CSS都只应用于组件本身。shadow DOM 最有用的功能是作用域 CSS:
- 外部页面中的 CSS 选择器不应用于组件内部。
- 内部定义的样式也不会渗出。它们的作用域仅限于宿主元素。
默认情况下,自定义元素从周围的CSS中继承一些属性,例如颜色和字体等。如果你想清空组件的初始状态并将组件内的所有CSS都设置为默认的初始值,那你可以这样操作:
:host {
all: initial;
}
需要注意的一点是,从外部定义在组件本身的样式优先于使用:host在Shadow DOM中定义的样式。如果你如下图这样做,它将会被覆盖。
<style>
*{
margin: 0;
padding: 0;
}
.video{
color: blue;
}
</style>
<template id="tpl">
<style>
:host{
color: red;
}
</style>
<!-- <link rel="stylesheet" href="./root.css"> -->
<h1>三国演义电影</h1>
<!-- <slot name="btn"></slot> -->
</template>
那么想要在外部去改变自定义元素的样式,正确的打开方式应该是怎么样呢?其实我们可以通过CSS变量去实现这个效果,例如:
<style>
*{
margin: 0;
padding: 0;
}
.video{
--color: blue;
}
</style>
<template id="tpl">
<style>
:host{
--color: red;
}
h1{
color: var(--color);
}
</style>
<h1>三国演义电影</h1>
</template>
在上面的示例中,我们使用行内<style>元素为Shadow DOM添加样式,但是我们完全可以通过<link>标签引用外部样式表来替代行内样式。
<template id="tpl">
<style>
/* :host{
--color: red;
}
h1{
color: var(--color);
} */
</style>
<link rel="stylesheet" href="./root.css">
<h1>三国演义电影</h1>
</template>
很多同学会发现baidu或者google上有些文章在介绍::shadow /deep/这些特性,但很遗憾告诉你,这都不能使用了。因为浏览器已经决定抛弃他们了。(详情参考https://developers.google.com/web/updates/2017/10/remove-shadow-piercing?hl=zh-cn)
友情提示:各位同学在查看网上文章的时候最后看一下文章的发布时间,避免学到古老级别的技术
2、css module
css module(https://github.com/css-modules/css-modules)是什么呢?官方的回答是:
A CSS Module is a CSS file in which all class names and animation names are scoped locally by default. –所有的类名和动画名称默认都有各自的作用域的CSS文件。
它和Shadow DOM不一样,它不是一个W3C标准,也不是一个浏览器特征,而是在构建步骤(例如使用Webpack或Browserify)中对CSS类名和选择器限定作用域的一种方式(类似于命名空间)。
我们该怎么使用它呢?首先来一起看一个webpack的例子:
一个平平无奇的webpack项目,我们只需要在webpack.config.js中加入以下代码:
{
test: /\.css$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
}
这样就完成一个简单的配置了~当然,这里还有几个必须的模块需要安装:
//App.css
.list{
background: yellow;
}
import styles from './App.css';
<ul className={styles.list}>
<li>hello word</li>
</ul>
我们可以看到className 重命名了,加了一些hash值从而达到我们想要的效果。
3、dynamic stylesheet
应用切出/卸载后,同时卸载掉其样式,浏览器就会对所有样式表的插入、移除做整个 CSS DOM 的重构,从而达到插入、卸载样式的目的。
例:
// reset.css
p{
background: red;
}
// reset2.css
p{
background: yellow;
}
// body
<div id="root"></div>
<template id="app1">
<div>
<link rel="stylesheet" href="./reset.css">
<p>app1</p>
</div>
</template>
<template id="app2">
<div>
<link rel="stylesheet" href="./reset2.css">
<p>app2</p>
</div>
</template>
<script>
function change () {
const app = window.location.href.split('#/')[1]
const root = document.querySelector('#root')
root.innerHTML = ''
root.appendChild(document.importNode(document.querySelector('#' + app).content, true))
}
change()
window.addEventListener("hashchange", change);
</script>
聪明的小伙伴可能发现了,在这里我们把样式放进了一个div里面【传统的做法都是把这些放在head里面的】。但其实不仅仅是样式,我们还可以直接把整个html 都扔进一个div里面。
结语