diff --git a/docs/en-us/changelog.md b/docs/en-us/changelog.md index 0bd97d626..eee7136e4 100644 --- a/docs/en-us/changelog.md +++ b/docs/en-us/changelog.md @@ -7,6 +7,14 @@ - 修订版本号:每周末会进行日常 bugfix 更新。(如果有紧急的 bugfix,则任何时候都可发布) --- +### 0.8.4 + +`2022-01-25` + +- **Bug Fix** + + - 🐞 Fixed the problem that the execution speed of style isolation is too slow in Firefox browser 80 and above. + ### 0.8.3 diff --git a/docs/zh-cn/changelog.md b/docs/zh-cn/changelog.md index d6962880a..82117ad7d 100644 --- a/docs/zh-cn/changelog.md +++ b/docs/zh-cn/changelog.md @@ -8,6 +8,15 @@ --- +### 0.8.4 + +`2022-01-25` + +- **Bug Fix** + + - 🐞 修复了在火狐浏览器80及以上版本中,样式隔离执行速度过慢的问题。 + + ### 0.8.3 `2022-01-20` diff --git a/package.json b/package.json index 1de21d2f9..2e4ed7527 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@micro-zoe/micro-app", - "version": "0.8.3", + "version": "0.8.4", "description": "A lightweight, efficient and powerful micro front-end framework", "private": false, "main": "lib/index.min.js", diff --git a/src/__tests__/source/scoped_css.test.ts b/src/__tests__/source/scoped_css.test.ts index e2d97dd32..7c9601181 100644 --- a/src/__tests__/source/scoped_css.test.ts +++ b/src/__tests__/source/scoped_css.test.ts @@ -89,12 +89,12 @@ describe('source scoped_css', () => { setAppName('test-app3') // 动态创建style const dynamicStyle = document.createElement('style') - dynamicStyle.textContent = '@font-face {font-family: test-font;} @media screen and (max-width: 300px) {body {background:lightblue;}} @supports (display: grid) {div {display: grid;}}' + dynamicStyle.textContent = '@font-face {font-family: test-font;} @media screen and (max-width: 300px) {body {background:lightblue;}} @supports (display: grid) {div {display: grid;}} @unknown {}' document.head.appendChild(dynamicStyle) defer(() => { - expect(dynamicStyle.textContent).toBe('@font-face {font-family: test-font;} @media screen and (max-width: 300px) {micro-app[name=test-app3] micro-app-body{background:lightblue;}} @supports (display: grid) {micro-app[name=test-app3] div{display: grid;}}') + expect(dynamicStyle.textContent).toBe('@font-face {font-family: test-font;} @media screen and (max-width: 300px) {micro-app[name=test-app3] micro-app-body{background:lightblue;}} @supports (display: grid) {micro-app[name=test-app3] div{display: grid;}} micro-app[name=test-app3] @unknown{}') resolve(true) }) }, false) @@ -381,8 +381,51 @@ describe('source scoped_css', () => { document.head.appendChild(dynamicStyle6) expect(dynamicStyle6.textContent).toBe('micro-app[name=test-app11] .test1{color: re/d;}') + // keep separator info + const dynamicStyle7 = document.createElement('style') + dynamicStyle7.textContent = '.test1, .test2 {color: red}' + document.head.appendChild(dynamicStyle7) + expect(dynamicStyle7.textContent).toBe('micro-app[name=test-app11] .test1, micro-app[name=test-app11] .test2{color: red}') + resolve(true) }, false) }) }) + + // 火狐浏览器中编码result + test('encode result in firefox', async () => { + const rawUserAgent = navigator.userAgent + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:96.0) Gecko/20100101 Firefox/96.0', + writable: true, + configurable: true, + }) + + const microAppElement12 = document.createElement('micro-app') + microAppElement12.setAttribute('name', 'test-app12') + microAppElement12.setAttribute('url', `http://127.0.0.1:${ports.scoped_css}/dynamic/`) + + appCon.appendChild(microAppElement12) + await new Promise((resolve) => { + microAppElement12.addEventListener('mounted', () => { + setAppName('test-app12') + // 动态创建style + const dynamicStyle = document.createElement('style') + dynamicStyle.textContent = '#root {color: red;}' + + document.head.appendChild(dynamicStyle) + + defer(() => { + expect(dynamicStyle.textContent).toBe('micro-app[name=test-app12] #root{color: red;}') + resolve(true) + }) + }, false) + }) + + Object.defineProperty(navigator, 'userAgent', { + value: rawUserAgent, + writable: true, + configurable: true, + }) + }) }) diff --git a/src/libs/utils.ts b/src/libs/utils.ts index 509702664..98132912f 100644 --- a/src/libs/utils.ts +++ b/src/libs/utils.ts @@ -374,3 +374,7 @@ export function getRootContainer (target: HTMLElement | ShadowRoot): HTMLElement export function trim (str: string): string { return str ? str.replace(/^\s+|\s+$/g, '') : '' } + +export function isFireFox (): boolean { + return navigator.userAgent.indexOf('Firefox') > -1 +} diff --git a/src/source/scoped_css.ts b/src/source/scoped_css.ts index d04032e8d..4b20471e6 100644 --- a/src/source/scoped_css.ts +++ b/src/source/scoped_css.ts @@ -1,6 +1,6 @@ /* eslint-disable no-useless-escape, no-cond-assign */ import type { AppInterface } from '@micro-app/types' -import { CompletionPath, getLinkFileDir, logError, trim } from '../libs/utils' +import { CompletionPath, getLinkFileDir, logError, trim, isFireFox } from '../libs/utils' import microApp from '../micro_app' // common reg @@ -46,7 +46,7 @@ class CSSParser { this.baseURI = baseURI this.linkPath = linkPath || '' this.matchRules() - return this.result + return isFireFox() ? decodeURIComponent(this.result) : this.result } public reset (): void { @@ -70,14 +70,14 @@ class CSSParser { // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule private matchStyleRule (): boolean | void { - const selectorList = this.formatSelector() + const selectors = this.formatSelector(true) // reset scopecssDisableNextLine this.scopecssDisableNextLine = false - if (!selectorList) return parseError('selector missing', this.linkPath) + if (!selectors) return parseError('selector missing', this.linkPath) - this.result += (selectorList as Array).join(', ') + this.recordResult(selectors) this.matchComments() @@ -88,6 +88,33 @@ class CSSParser { return true } + private formatSelector (skip: boolean): false | string { + const m = this.commonMatch(/^([^{]+)/, skip) + if (!m) return false + + return m[0].replace(/(^|,[\n\s]*)([^,]+)/g, (_, separator, selector) => { + selector = trim(selector) + if (!( + this.scopecssDisableNextLine || + ( + this.scopecssDisable && ( + !this.scopecssDisableSelectors.length || + this.scopecssDisableSelectors.includes(selector) + ) + ) || + rootSelectorREG.test(selector) + )) { + if (bodySelectorREG.test(selector)) { + selector = selector.replace(bodySelectorREG, this.prefix + ' micro-app-body') + } else { + selector = this.prefix + ' ' + selector + } + } + + return separator + selector + }) + } + // https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration private styleDeclarations (): boolean | void { if (!this.matchOpenBrace()) return parseError("Declaration missing '{'", this.linkPath) @@ -121,7 +148,7 @@ class CSSParser { }) } - this.result += cssValue + this.recordResult(cssValue) } // reset scopecssDisableNextLine @@ -139,39 +166,6 @@ class CSSParser { return this.matchAllDeclarations() } - private formatSelector (): boolean | Array { - const m = this.commonMatch(/^([^{]+)/, true) - if (!m) return false - return trim(m[0]) - .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '') - .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, (r) => { - return r.replace(/,/g, '\u200C') - }) - .split(/\s*(?![^(]*\)),\s*/) - .map((s: string) => { - const selectorText = s.replace(/\u200C/g, ',') - if (this.scopecssDisableNextLine) { - return selectorText - } else if (this.scopecssDisable) { - if ( - !this.scopecssDisableSelectors.length || - this.scopecssDisableSelectors.includes(selectorText) - ) { - return selectorText - } - } - - if (selectorText === '*') { - return this.prefix + ' *' - } else if (bodySelectorREG.test(selectorText)) { - return selectorText.replace(bodySelectorREG, this.prefix + ' micro-app-body') - } else if (rootSelectorREG.test(selectorText)) { // ignore root selector - return selectorText - } - return this.prefix + ' ' + selectorText - }) - } - private matchAtRule (): boolean | void { if (this.cssText[0] !== '@') return false // reset scopecssDisableNextLine @@ -242,7 +236,7 @@ class CSSParser { private pageRule (): boolean | void { if (!this.commonMatch(/^@page */)) return false - this.formatSelector() + this.formatSelector(false) // reset scopecssDisableNextLine this.scopecssDisableNextLine = false @@ -295,7 +289,7 @@ class CSSParser { return () => { if (!this.commonMatch(reg)) return false this.matchLeadingSpaces() - return false + return true } } @@ -334,7 +328,7 @@ class CSSParser { // get comment content let commentText = this.cssText.slice(2, i - 2) - this.result += `/*${commentText}*/` + this.recordResult(`/*${commentText}*/`) commentText = trim(commentText.replace(/^\s*!/, '')) @@ -368,7 +362,7 @@ class CSSParser { if (!matchArray) return const matchStr = matchArray[0] this.cssText = this.cssText.slice(matchStr.length) - if (!skip) this.result += matchStr + if (!skip) this.recordResult(matchStr) return matchArray } @@ -384,6 +378,16 @@ class CSSParser { private matchLeadingSpaces (): void { this.commonMatch(/^\s*/) } + + // splice string + private recordResult (strFragment: string): void { + // Firefox is slow when string contain special characters, see https://github.com/micro-zoe/micro-app/issues/256 + if (isFireFox()) { + this.result += encodeURIComponent(strFragment) + } else { + this.result += strFragment + } + } } /**