6

style-loader与iframe的问题

 2 years ago
source link: https://swordair.com/style-loader-and-iframe-problems/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

style-loader与iframe的问题

通常情况下其实遇不到style-loader与iframe纠缠的情况,不过由于自己所做项目的特殊性,所以不得不经常与iframe打些交道,并且往往遇到问题能参考的资料也非常有限。

style-loader是webpack的常用插件,作用是将CSS注入进DOM。正因其注入CSS是根据所处运行环境决定,所以如果页面中存在iframe的话,那么就会存在样式注入点与期望不同的情况。由于页面代码可能无法调整执行环境,而样式也无法再不同文档环境相互影响,所以有必要调整注入点。根据情况的不同,运行于父窗口向子iframe注入样式,以及运行于子iframe反过来向父窗口注入,甚至是多层嵌套iframe的情况等都可能遇到(至少我都遇到了)。

在保证同源的大前提下,好在大部分的样式注入库都提供了挂载点变更的配置(比如更早前自己遇到的JSS的修改注入点),甚至是运行时方法,style-loader也不例外,提供了相关配置insert: https://github.com/webpack-contrib/style-loader#insert。由于是所用于打包流程所以也只有配置可用。

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          {
            loader: 'style-loader',
            options: {
              insert: 'body',
            },
          },
          'css-loader',
        ],
      },
    ],
  },
};

虽然文档并没有展示对insert配置一个function的例子和说明,但insert确实支持`{String|Function}`。既然没有特别说明清楚,那么可以查看一下其源代码确认使用方式,关键代码(insertStyleElement):

if (typeof options.insert === 'function') {
    options.insert(style);
  } else {
    const target = getTarget(options.insert || 'head');

    if (!target) {
      throw new Error(
        "Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."
      );
    }

    target.appendChild(style);
  }

由此可见如果配置了一个function回调,那么运行时就会优先执行方法,并传入已经生成好的style元素,所以,在这个回调里只需要简单的获取到相应的窗体即可。这一配置相当灵活和强大,可以在多层嵌套的iframe里精确指定注入点

{
  test: /\.css$/i,
  exclude: /src/,
  use: [
    {
      loader: 'style-loader',
      options: {
        insert: function(style) {
          // find iframe document here, parent or children
          var head = window.parent.parent.document.querySelector('head');
          head.appendChild(style);
        },
      }
    },
    'css-loader'
  ]
}

虽然大部分问题已经迎刃而解,遗憾的是,我自己遇到过的诸多问题并没有这么简单的被全部解决。回到之前的各种场景的问题,同窗体如果只是要改变注入点位置,那么指定insert的target就可以了,但如果牵扯到到iframe,那么还会催生出好几种情况需要继续处理。

其一,父窗体往子iframe注入,虽然通过上面代码中的insert回调可以准确注入,但往往遇到注入时子窗体还没有写入DOM,出现在用前端渲染iframe的情况,此时加载代码已经准备注入样式,而iframe却还没有就位。我没能找到非常顺的处理方式,由于自己是在做iframe开发环境时遇到的,所以就变通了一下,将样式注入在一个临时区域中,然后在iframe中通过MutationObserver来将样式的变动同步过来。

其二,insert的配置必须用es5的写法,由于通常并不编译配置文件,而webpack的这个配置是直接将方法打包进运行代码里的,所以会保持代码原样。所以如果写作箭头函数,将会在低版本浏览器遇报错。

其三,与iframe打交道,时刻要注意注入的范围和影响面,通过使用exclude,include等配置,只控制所需的最小样式。

做完这一茬,基本style-loader与iframe的问题就告一段了。在实际项目中,和iframe纠缠在一起的,往往还有react-dom,还有requirejs等等,大部分都牵扯一个挂载点(注入点)问题,解决这些问题需要时刻清晰的意识到代码在哪里运行,到底是哪个window对象。当然为了保住发际线,还是不要和iframe牵扯过多为妙:)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK