lisonge / vite-plugin-monkey

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

构建时默认启用 lib 模式以避免不必要的动态导入 preload 处理和额外的 systemjs 引入 #99

Closed enpitsuLin closed 1 year ago

enpitsuLin commented 1 year ago

由于使用 unocss 还有老哥的提的 pr 的解决方案没有很好的解决 unocss 在这个插件中使用的问题,后来我自己简单的解决了下开发环境下面 unocss 会错误注入到目标网页 head 中的问题。

直接把 unocss 的 resolve 区分开发生产模式手动直接引入(生产时通过 unocss cli 封装的插件处理产物 ?raw 的问题),而不是走 vite 的逻辑 see here

虽然实际上开发模式不应该用 style标签的形式插入,应该手动实现一次 vite 逻辑来满足 HMR,但不是很重要,主要是后来构建上出现了点问题。(这个逻辑是不是可以通过 vite-plugin-monkey 提供一个虚拟模块来处理 resloveId + 生成合适的 Element)

Vite 构建在没有启用 lib 模式的情况下,对于上面我在生产中做的动态导入是会自动分成两个 chunk 然后通过插件引入 systemjs 解决这里,但是在 generateBundle 这里 (实际上是 L621 这里处理 deps 的代码)似乎因为什么原因被跳过了,于是构建出了有问题的代码

image

此处的 __VITE_PRELOAD__ 应该会被替换成合适的 deps chunk 数组——在我的项目中应该是空

如果说这部分是 vite-plugin-monkey 错误的跳过了这个 hook 那这个问题应该是 bug


但是后面发现实际上对于 vite 处理这样的动态导入会添加 preload 可以通过启用 lib 模式解决,不仅解决了我的问题还不会额外引入 systemjs,直接被编译成了一个 Promise.resolve + 字符串形式,也正好达到了我的目标

image


所以我在想是不是可以在构建时默认启用 lib 来避免类似的动态导入的问题顺便还能去掉 systemjs 的引入?

enpitsuLin commented 1 year ago

虽然实际上开发模式不应该用 style标签的形式插入,应该手动实现一次 vite 逻辑来满足 HMR,但不是很重要,主要是后来构建上出现了点问题。(这个逻辑是不是可以通过 vite-plugin-monkey 提供一个虚拟模块来处理 resloveId + 生成合适的 Element)

实际上 HMR 稍微卡住了😅人家用的 vite updateStyle 似乎还是需要插件来处理

比较粗糙的实现了专门针对 unocss 的 hmr 我觉得通用化应该也不复杂,提供某种方式可以提供将 css 导入成 HTMLStyleElement 的功能

hmr-fix

lisonge commented 1 year ago

在 vite build 阶段提前去 build 确实是一个很好的想法

下面代码是根据 userscript/plugins/build.ts 改进后的代码,将 unocss 转为 style 对象,并且自动根据构建环境使用不同的 unocss 代码,当 unocss hmr 时,自动同步 css 代码,而浏览器端代码只需要简单导入一个 style 对象即可


// packages/userscript/plugins/build.ts
import { tmpdir } from 'node:os';
import { resolve } from 'node:path';
import { readFile } from 'node:fs/promises';
import process from 'node:process';
import { build } from '@unocss/cli';
import Unocss from 'unocss/vite';
import { type Plugin } from 'vite';

const template = `
const style = document.createElement('style');
style.textContent = __TEXT__;
if (import.meta.env.DEV) {
  (async () => {
    while (true) {
      const unoStyle = document.querySelector(
        'head style[data-vite-dev-id="/__uno.css"]',
      );
      if (unoStyle) {
        style.textContent = unoStyle.textContent;
        new MutationObserver(() => {
          if (style.textContent != unoStyle.textContent) {
            style.textContent = unoStyle.textContent;
          }
        }).observe(unoStyle, {
          childList: true,
          attributes: true,
        });
        return;
      }
      await new Promise((res) => setTimeout(res, 500));
    }
  })();
}
export default style;
`.trim();

export function UnocssBuildPlugin(isBuild: boolean): Plugin[] {
  return [
    ...(isBuild ? [] : Unocss()),
    {
      name: 'unocss:style',
      resolveId(source) {
        if (source === 'unocss?style') return '/__unocss_style';
      },
      async load(id) {
        if (id === '/__unocss_style') {
          if (isBuild) {
            const outFile = resolve(tmpdir(), `unocss_${+new Date()}.css`);
            await build({
              outFile,
              cwd: process.cwd(),
              config: resolve(__dirname, '../unocss.config.ts'),
              patterns: ['./src/**/*.vue', './src/**/*.css'],
              stdout: false,
            });
            const css = (await readFile(outFile))
              .toString()
              .split('\n')
              .join('')
              .trim();

            return {
              code: template.replace(`__TEXT__`, JSON.stringify(css)),
            };
          } else {
            return {
              code:
                `import 'uno.css';` +
                template.replace(`__TEXT__`, JSON.stringify('')),
            };
          }
        }
      },
    },
  ];
}
// vite.config.ts
// Omitting unrelated code / 省略无关代码
import { UnocssBuildPlugin } from './plugins/build';

export default defineConfig(async ({ command }) => {
  return {
    plugins: [
      AutoImport({ }),
      Component({ dts: 'src/components.d.ts' }),
      VueI18nPlugin({ }),

      UnocssBuildPlugin(command=='build'), // here

      vue(),
      monkey({
        entry: 'src/main.ts',
      }),
    ],
  };
});
// packages/userscript/src/main.ts
// Omitting unrelated code / 省略无关代码
import style from 'unocss?style'

function initUserjsDigger(attachShadow = HTMLElement.prototype.attachShadow) {
  customElements.define(
    'userjs-digger',
    class extends HTMLElement {
      app: VueApp
      constructor() {
        super()
        const shadow = attachShadow.call(this, { mode: 'open' })

        shadow.appendChild(style) // here

        shadow.appendChild(app)
        this.app = createApp(App)
        this.app.use(i18n)
        this.app.provide('container', app)
        this.app.mount(app)
      }
    },
  )

  const userDigger = document.createElement('userjs-digger')
  document.body.append(userDigger)
}
lisonge commented 1 year ago

我的意思是 没必要改 vite-plugin-monkey 的代码

你直接用上面改进的自定义插件也能在 web components 内部无缝使用 unocss,而且 unocss hmr 也能正常使用

enpitsuLin commented 1 year ago

嗯 我本来以为 动态引入会添加 preload 并执行错误的逻辑,测试了下应该是只有 unocss 才会出现这样的情况,那我自己处理就ok了

lisonge commented 1 year ago

使用 iframe 可以避免宿主环境创建 style[data-vite-dev-id="/__uno.css"] 标签

如果宿主环境也使用 css 原子类,这可以避免脚本 unocss 样式在 dev 模式影响宿主样式

改进后的代码完整示例 test-example