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

link element load event not fired on old browser. #294

Open
lili21 opened this issue Oct 11, 2018 · 9 comments
Open

link element load event not fired on old browser. #294

lili21 opened this issue Oct 11, 2018 · 9 comments

Comments

@lili21
Copy link

lili21 commented Oct 11, 2018

MCEP use load event to resolve the promise, while the event is not supported well on some old browser, like Android 4.3 webview.

It breaks the webpack dynamic import feature, cause the promise will never be resolved.

// index.js
import('./app.js').then(() => {
  // the function will never be called
  console.log('App Loaded')
})
// app.js
import './app.css'
console.log('Hello, app')

Reproduce repo

@alexander-akait
Copy link
Member

@lili21 webpack everywhere uses Promises for loading async chunk, please use Promise polyfills, also your example about loading async js file, it is not related to this repo. If you think webpack doing something wrong please create issue in webpack repo

@lili21
Copy link
Author

lili21 commented Oct 11, 2018

@evilebottnawi I don't think you understand the issue. maybe it's my fault, my english isn't very good.

It's not about Promise polyfills, and it's not the async js file issue. If you remove the import './app.css' code, it will works as expecting.

and I think the issue is relate to this repo. as I mentioned before, the plugin using load event to detect the async css file be loaded and resolve the promise, but load event isn't supported well.

the example code logic is something like below,

const p1 = loadAsyncJs()
const p2 = loadAsyncCss()

Promise.all([p1, p2]).then(() => {
  // the function will never be called
  console.log('App Loaded')
})

and p2 will never be resolved.

@alexander-akait
Copy link
Member

@lili21 can you create minimum reproducible test repo and affected browsers?

@lili21
Copy link
Author

lili21 commented Oct 11, 2018

Yeah. here you go

I only tested it on Android mobile. Android 4.4+ is good. Android 4.3 isn't.

@alexander-akait
Copy link
Member

@lili21 Looks related #134, but solution is not very good, wee need search better, feel free to investigate

@moyus
Copy link

moyus commented Oct 16, 2018

same error here. When use webpack dynamic import(), all created css is not loaded in old android 4.3 browsers, which lead to async module not resolved.

@lili21
Copy link
Author

lili21 commented Oct 17, 2018

You can extract all css in a single file to prevent this, But it's bad for performance.

@visormatt
Copy link

@lili21 I wouldn't say it's necessarily bad for performance as we have a single CSS chunk that contains the entire applications CSS at 7kb. Depending on the final size the argument can be made that the additional requests may not be necessary. CSS Modules and a well groomed base styleguide can do wonders 🎉

@Jessidhia
Copy link
Contributor

Jessidhia commented Jan 18, 2019

I present to you this horribleness as a workaround, which does work on Chrome 14, one of the affected browsers according to https://pie.gd/test/script-link-events/:

image

I did not actually test this with mini-css-extract-plugin, but I did write the same code as the one mini-css-extract-plugin would try to run when you import() an extracted file. The important code is inside function polyfill():

// @ts-check

// Used for the test code
import 'core-js/fn/promise'

// Needed for the polyfill itself
// Chrome 14 didn't have this either
import MutationObserver from 'mutation-observer'

polyfill()
// Fun fact: can't import this over https, github's https is too new for Chrome 14
testImport('http://necolas.github.io/normalize.css/8.0.1/normalize.css').then(
  function() {
    console.log('Promise resolved!')
  }
)

/**
 *
 * @param {string} url
 */
function testImport(url) {
  // this code is the same as the one the webpack runtime will emit
  return new Promise(function(resolve) {
    var link = document.createElement('link')
    link.rel = 'stylesheet'
    link.type = 'text/css'
    link.crossOrigin = 'anonymous'
    link.onload = resolve
    link.href = url
    document.getElementsByTagName('head')[0].appendChild(link)
  })
}

function polyfill() {
  var webkitVersionMatch = navigator.userAgent.match(
    /AppleWebKit\/(\d+(?:\.\d+)+)/
  )
  var webkitVersion = webkitVersionMatch && parseFloat(webkitVersionMatch[1])
  // 535.24 is the first version listed as working
  // in https://pie.gd/test/script-link-events/
  if (webkitVersion === null || webkitVersion >= 535.24) {
    return
  }
  var copyCrossOrigin = document.createElement('link').crossOrigin !== undefined

  var mo = new MutationObserver(function(events) {
    events.forEach(function(event) {
      event.addedNodes.forEach(function(node) {
        // prettier-ignore
        if (/** @type {Element} */ (node).tagName === 'LINK') {
          handleLinkTag(/** @type {HTMLLinkElement} */ (node))
        }
      })
    })
  })
  mo.observe(document.getElementsByTagName('head')[0], {
    childList: true
  })

  /**
   * @param {HTMLLinkElement} tag
   */
  function handleLinkTag(tag) {
    if (
      tag.rel !== 'stylesheet' ||
      tag.type !== 'text/css' ||
      tag.onload === null
    ) {
      // don't care
      return
    }
    setTimeout(dispatch)

    function dispatch() {
      var img = new Image()
      // webpack doesn't care about the resolved value, only that it resolved
      img.onerror = /** @type {() => void} */ (tag.onload)
      img.onload = tag.onload
      // NOTE: only copy crossOrigin if the browser supports crossOrigin to begin with
      if (copyCrossOrigin) {
        img.crossOrigin = tag.crossOrigin
      }
      img.src = tag.href
    }
  }
}

This does, of course, make it impossible to detect real errors when loading the script; the Promise will always resolve.

You can replace the contents of the dispatch function with your favorite alternative hack if the Image tag hack isn't good enough 😉

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

No branches or pull requests

5 participants