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

shadow-dom 模式下 css 不挂载到全局 #77

Closed
mokeyish opened this issue Apr 21, 2023 · 15 comments
Closed

shadow-dom 模式下 css 不挂载到全局 #77

mokeyish opened this issue Apr 21, 2023 · 15 comments

Comments

@mokeyish
Copy link

mokeyish commented Apr 21, 2023

这个插件不错👍,先前是自己写 vite 脚本调整的,但是 dev 模式并不好,看到你这个插件,想切换过来。但是发现这个插件默认把样式注入到全局了,没看到关的地方。对于油猴插件为了不受全局样式影响,shadow-dom 是不错的选择。因此需要把样式替换到 /* global-placeholder */ 占位符上。

两个方案

  1. 关闭此插件的 css 全局注入
  2. 集成 shadow-dom 模式的 /* global-placeholder */ 占位符替换。

怎么快速让样式插入到我这个占位符上呢?

图片

先前写过的脚本。

const bundle = (mode: string): PluginOption => {
  const placeholder = '/* global-placeholder */';
  let global_css: string | null = null;
  return {
    name: 'build:bundle',
    enforce: 'post',
    async buildStart() {
      global_css = null
    },
    generateBundle(_options, bundle) {
      console.log('');
      console.log('=======>', Object.keys(bundle));

      const css_assets = Object.values(bundle).map(o => o.type === 'asset' && o.fileName.endsWith('.css') ? o : undefined).filter(o => o !== undefined);
      if (css_assets.length > 0) {
        global_css = css_assets.map(o => (typeof o.source === 'string' ? o.source : new TextDecoder().decode(o.source)).trim()).join(' ').replace(/\\/g, '\\\\') ?? '';
      }

      if (!global_css) {
        return;
      }

      let replaced = false;
      for (const chunk of Object.values(bundle)) {

        if (chunk.type === 'chunk') {
          const code = typeof chunk.code === 'string' ? chunk.code : new TextDecoder().decode(chunk.code);
          if (code.includes(placeholder)) {
            chunk.code = `${banner}${code.replace(placeholder, global_css)}`;
            replaced = true;
          }
        }
      }

      if (mode === 'production' && replaced) {
        for (const key of Object.keys(bundle)) {
          const file = bundle[key];
          if (file.type === 'asset' && file.fileName.endsWith('.css')) {
            delete bundle[key];
          }
        }
      }

    }
  }
};
@lisonge lisonge added the enhancement New feature or request label Apr 21, 2023
@mokeyish
Copy link
Author

mokeyish commented Apr 21, 2023

图片

图片

我实现了 Build 的 placeholder 替换,请问 serve dev 下的替换怎么搞?

@mokeyish
Copy link
Author

mokeyish commented Apr 21, 2023

@lisonge 你好,我提交了 PR, vite build 生成可以的。请指导下,怎么让 placeholder 在 vite serve dev 下生效?

@lisonge
Copy link
Owner

lisonge commented Apr 21, 2023

你的改动会造成 serve 与 build 模式下的行为不一致

import './xxx.css' 这类代码的 副作用 被 vite 视为全局样式

vite 目前暂未支持 shadow-dom 下 css vitejs/vite#12206

目前看起来 shadow-dom 下的 css 只能使用 css?inline 实现,而且失去了 hmr

import cssText from './style.css?inline';

const style = document.createElement('style');
style.textContent = cssText;
shadow.appendChild(style);

@lisonge
Copy link
Owner

lisonge commented Apr 21, 2023

vite 下使用 Web components 的 lit 示例

template-lit-ts/src/my-element.ts

也是使用 内联 css

@mokeyish
Copy link
Author

我用了 unocss ,没有单独的 style.css 文件,是动态生成的。

@mokeyish
Copy link
Author

unocss 的 shadow-dom 的 placeholder 也好像有问题,它只管那个组件文件的 css,像油猴插件,其实就是最外层的组件才是 shadow-dom,需要把所有样式收集,放到 shadow–dom 的 <style>标签里</style>.

@lisonge
Copy link
Owner

lisonge commented Apr 21, 2023

你为什么不在 vite-plugin-monkey 前面加一个插件把 css 移动到 js 里

这样没必要修改 vite-plugin-monkey 源代码

收集 css 的代码你可以直接复用

const cssBundleList = Object.entries(esmBundle).filter(([k]) =>
k.endsWith('.css'),
);
const cssList: string[] = [];
cssBundleList.forEach(([k, v]) => {
if (v.type == 'asset') {
cssList.push(v.source.toString());
delete esmBundle[k];
}
});
if (cssList.length > 0) {
let css = cssList.join('');
if (!viteConfig.build.minify && finalOption.build.minifyCss) {
const { cssTarget, target } = viteConfig.build;
const finalCssTarget = getFinalTarget(cssTarget || target || []);
css = (
await transformWithEsbuild(css, 'any_name.css', {
sourcemap: false,
legalComments: 'none',
minify: true,
target: finalCssTarget,
})
).code.trimEnd();
}

然后只需要把

finalOption.injectCssCode = await miniCode(
fn2string(cssInjectFn, '\x20' + css + '\x20'),
// use \x20 to compat unocss, see https://github.com/lisonge/vite-plugin-monkey/issues/45
// TODO check the order of plugin-monkey is last in vite plugin list, if not, logger warn message
'js',
);

换成

        Object.values(esmBundle).forEach((chunk) => {
          if (chunk.type == 'chunk') {
            chunk.code = chunk.code.replace(`/* global-placeholder */`, css);
          }
        });

思路差不多是这样

@mokeyish
Copy link
Author

嗯,好像也可以。把我之前写的脚本放在 vite-plugin-monkey 之前就行了。到 vite-plugin-monkey 就没 css 给它注入了。

build 和 serve 行为不一致,你有什么思路,也通过插件来快捷的解决吗?上面你引用的 PR 貌似没那么容易合并。

@lisonge
Copy link
Owner

lisonge commented Apr 21, 2023

可以试试这个 https://github.com/web-widget/vite-plugin-shadow-dom-css 但是我不知道是否有用

反正我用起来直接报错


build 和 serve 行为不一致,你有什么思路,也通过插件来快捷的解决吗?

如果导入的 css 都是 styleDom 对象,将副作用交给开发者实现,似乎可以实现 shadow-dom 下 css 的 hmr,同时保持 serve 和 build 下的一致

import styleDom from './style.css?style';
import unocssStyleDom from 'uno.css?style';

customShadow.appendChild(style);

hmr 的时候内部模块更改 styleDom.textContent 即可,也不影响外部模块

由于是同一个 styleDom 引用, shadow-dom 下的 style.textContent 也会自动更改

@lisonge lisonge removed the enhancement New feature or request label Apr 21, 2023
@mokeyish
Copy link
Author

谢谢你的耐心解答,我改成这样完美解决了,开发模式去 Head 上取样式,放到 shadow dom 里,生产模式就用 placeholder 替换。

SolidJs 完美啊,代码可以写的这么简洁。

图片

@lisonge
Copy link
Owner

lisonge commented Apr 21, 2023

如果 unocss 支持直接导出 原生 style 对象,生产模式 额外的插件 也不需要了

import unocssStyle from 'virtual:uno.css?style';

render(
  () => (
    <Portal useShadow>
        {unocssStyle}
        <App />
      </Portal>
  ),
  document.body,
);```

@mokeyish
Copy link
Author

我上面的写法相当于启动后将 head 里的 styledom 对象移动到了 shadowdom 里,hmr 也都正常。

@yunsii
Copy link

yunsii commented Sep 13, 2024

使用 unocss + shadown dom 的话现在看起来还是得自行处理才能更好的 HMR?

@lisonge
Copy link
Owner

lisonge commented Sep 13, 2024

@UnluckyNinja
Copy link

我也遇到类似情况,用UnoCSS,外面套一层shadowDOM,但用的内联样式。
研究了几天,目前热更新和构建都可以了,但单文件组件内的style块不知道怎么引入到代码里,还没想到好的办法。
大致上就是UnoCSS里面加了一下内联的判断(默认情况resolveId阶段会把query丢掉),修改了virtual:uno.css转换使其兼容UnoCSS的HMR转换,然后开发模式下触发模块重载,构建模式下在JS里替换,详细方式写在这里了:unocss/unocss#4137 (comment) ,供各位参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants