mini-css-extract-plugin
mini-css-extract-plugin copied to clipboard
link element load event not fired on old browser.
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')
@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
@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.
@lili21 can you create minimum reproducible test repo and affected browsers?
@lili21 Looks related https://github.com/webpack-contrib/mini-css-extract-plugin/pull/134, but solution is not very good, wee need search better, feel free to investigate
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.
You can extract all css in a single file to prevent this, But it's bad for performance.
@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 🎉
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/:
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 😉