lisonge / vite-plugin-monkey

A vite plugin server and build your.user.js for userscript engine like Tampermonkey, Violentmonkey, Greasemonkey, ScriptCat
MIT License
1.31k stars 70 forks source link

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

Closed mokeyish closed 1 year ago

mokeyish commented 1 year ago

这个插件不错👍,先前是自己写 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];
          }
        }
      }

    }
  }
};
mokeyish commented 1 year ago

图片

图片

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

mokeyish commented 1 year ago

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

lisonge commented 1 year ago

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

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

vite 目前暂未支持 shadow-dom 下 css https://github.com/vitejs/vite/pull/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 commented 1 year ago

vite 下使用 Web components 的 lit 示例

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

也是使用 内联 css

mokeyish commented 1 year ago

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

mokeyish commented 1 year ago

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

lisonge commented 1 year ago

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

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

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

https://github.com/lisonge/vite-plugin-monkey/blob/704c47a7b5558b1769cbf1cacb3ad0cdccce0b2d/packages/vite-plugin-monkey/src/node/plugins/collectCss.ts#L19-L42

然后只需要把

https://github.com/lisonge/vite-plugin-monkey/blob/704c47a7b5558b1769cbf1cacb3ad0cdccce0b2d/packages/vite-plugin-monkey/src/node/plugins/collectCss.ts#L43-L48

换成

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

思路差不多是这样

mokeyish commented 1 year ago

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

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

lisonge commented 1 year ago

可以试试这个 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 也会自动更改

mokeyish commented 1 year ago

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

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

图片

lisonge commented 1 year ago

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


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

render(
  () => (
    <Portal useShadow>
        {unocssStyle}
        <App />
      </Portal>
  ),
  document.body,
);```
mokeyish commented 1 year ago

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