背景是需要将 import 'example.css' 的样式插入到 iframe 下。
网上有很多方案,但是都不是我想要的解决方法。
比如获得到样式的内容,然后插入到 iframe 下,但是我希望实现的是类似于 import 的实现方法。
那么查阅了相关资料发现,style-loader 有一个配置项 injectType: 'lazyStyleTag' ,支持后动插入 <style> 标签。
import styles from 'example.css'
styles.use()
是在调用 use 方法后对应的 <style> 标签才会插入到 document.head 中。由于我们的需求是插入到 iframe 下的 head 中去,所以我们还需要进行少许配置。
我们需要配置 insert 函数,让 use 方法的调用可以插入到指定的位置。
但是你会发现 use 方法不接受参数,所以我们需要使用一些手段来实现,当调用 use 方法时,能够顺利插入到指定位置。
我们需要看到 node_modules/style-loader/dist/index.js 的源码位置,可以看到 use 方法调用时会调用来自 ./runtime/injectStylesIntoStyleTag.js 的默认导出函数。并且传入上下文和 options 。
当然这不是重点,这是入口,持续执行后会看到进入到 insertStyleElement 函数。如果配置 insert 为函数,相当于调用 use 方法时逻辑自行处理。
回到 index.js 的文件位置,这里很关键的内容出现了,我们可以在 insert 函数定义时通过外部的环境来定义需要将样式插入的具体位置。
options({
  injectType: 'lazyStyleTag',
  insert: function (style: HTMLStyleElement) {
    var doc = globalThis.__example__ || document
    if (doc.head) {
      doc.head.appendChild(style)
    }
  }
})
当然,你仔细观察源码可以发现,其他还可以在 insert 函数中获取到 exported 这个变量,至于是为什么,相信你去看下 index.js 便会明白。
我们可以按照我们的需求,声明一下 d.ts:
declare module '*.css?lazy' {
  interface LazyCSS {
    locals: Record<string, any>
    use: () => void
    unuse: () => void
    __appendTo__?: Document
  }
  const lazycss: LazyCSS
  export default lazycss
}
我们就可以这样使用:
import styles from 'example.css?lazy'
styles.__appendTo__ = iframeDoc
styles.use()
当然, insert 函数也需要做稍许调整:
options({
  injectType: 'lazyStyleTag',
  insert: function (style: HTMLStyleElement) {
    var doc = exported.__appendTo__ || document
    if (doc.head) {
      doc.head.appendChild(style)
    }
  }
})
这里肯定会奇怪,为何后面加了一个参数 ?lazy ,是为了区分普通 import 样式和手动插入的参数。
结合完整的在 webpack-chain 中的配置如下:
const insert = function (style: HTMLStyleElement) {
  var doc = exported.__appendTo__ || document
  if (doc.head) {
    doc.head.appendChild(style)
  }
  // 可以加入任何你想加入的逻辑
}
config.module.rule('css')
  .oneOf('lazy-css')
  .before('normal')
  .resourceQuery(/lazy/)
  .test(/\.css$/)
  .use('style-loader')
  .loader('style-loader')
  .options({ injectType: 'lazyStyleTag', insert })
  .end()
  .use('css-loader')
  .loader('css-loader')
  .end()
  .use('postcss-loader')
  .loader('postcss-loader')
// 同样的,你也可以配置 module.css 也使用手动插入的形式
// 记住需要在原有的 loader 校验前加入
到这里,我们其实已经实现了我们的需求。也算是大功告成!
不过这里针对 CSS Module 这种形式记得注意 hash 值是否能够保持一致,若你的 npm 包需要提供这类能力,可以将样式在构建产出时将 hash 值编译成一个非 CSS Module 的样式文件,然后进行引入使用即可。
有关 style-loader 的源码可以点击查看。
同理,在别的工程化工具下,也可以借助这个思路实现手动插入样式的需求~
- 本文链接: https://zongzi531.github.io/2022/06/22/%E5%80%9F%E5%8A%A9webpack%E5%AE%9E%E7%8E%B0%E6%A0%B7%E5%BC%8F%E6%89%8B%E5%8A%A8%E6%8F%92%E5%85%A5/
 - 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!