Skip to content

Commit

Permalink
feat: new host style support based on style scope
Browse files Browse the repository at this point in the history
  • Loading branch information
LastLeaf committed Jul 31, 2024
1 parent ee90b13 commit 4b64562
Show file tree
Hide file tree
Showing 21 changed files with 311 additions and 281 deletions.
1 change: 1 addition & 0 deletions glass-easel-miniprogram-adapter/src/space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export class CodeSpace {
else this.waitingAliasMap[is] = [alias]
})
this._$sharedStyleScope = this.styleScopeManager.register('')
this.space.setSharedStyleScope(this._$sharedStyleScope)
this._$styleIsolationMap = Object.create(null) as { [path: string]: StyleIsolation }
}

Expand Down
1 change: 0 additions & 1 deletion glass-easel-miniprogram-webpack-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,6 @@ export class GlassEaselMiniprogramWebpackPlugin implements WebpackPluginInstance
} else {
x.options = {
classPrefix: styleSheetManager.getScopeName(compPath),
compPath,
setLowPriorityStyles: (s: string, map: string) => {
styleSheetManager.setLowPriorityStyles(compPath, s, map)
},
Expand Down
4 changes: 2 additions & 2 deletions glass-easel-miniprogram-webpack-plugin/wxss_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const { StyleSheetTransformer } = require('glass-easel-stylesheet-compiler')

module.exports = function (src, prevMap, meta) {
const callback = this.async()
const { classPrefix, compPath, setLowPriorityStyles } = this.query
const sst = new StyleSheetTransformer(this.resourcePath, src, classPrefix, 750, compPath)
const { classPrefix, setLowPriorityStyles } = this.query
const sst = new StyleSheetTransformer(this.resourcePath, src, classPrefix, 750, true)
setLowPriorityStyles(sst.getLowPriorityContent(), sst.getLowPrioritySourceMap())
const warnings = sst.extractWarnings()
if (warnings && warnings.length > 0) {
Expand Down
4 changes: 2 additions & 2 deletions glass-easel-stylesheet-compiler/src/js_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ impl StyleSheetTransformer {
s: &str,
class_prefix: Option<String>,
rpx_ratio: f32,
host_is: Option<String>,
convert_host: bool,
) -> Self {
let mut sst = crate::StyleSheetTransformer::from_css(
name,
s,
StyleSheetOptions {
class_prefix,
rpx_ratio,
host_is,
convert_host,
..Default::default()
},
);
Expand Down
61 changes: 37 additions & 24 deletions glass-easel-stylesheet-compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct StyleSheetOptions {
pub class_prefix_sign: Option<String>,
pub rpx_ratio: f32,
pub import_sign: Option<String>,
pub convert_host: bool,
pub host_is: Option<String>,
}

Expand All @@ -26,6 +27,7 @@ impl Default for StyleSheetOptions {
class_prefix_sign: None,
rpx_ratio: 750.,
import_sign: None,
convert_host: false,
host_is: None,
}
}
Expand Down Expand Up @@ -444,8 +446,7 @@ fn parse_qualified_rule(input: &mut StepParser, ss: &mut StyleSheetTransformer)
let mut in_class = false;
let mut has_whitespace = false;
input.skip_whitespace();
if let Some(host_is) = ss.options.host_is.as_ref() {
let quoted_host_is = Token::QuotedString(host_is.clone().into());
if ss.options.convert_host {
let r = input.try_parse::<_, _, ParseError<()>>(|input| {
input.expect_colon()?;
let Ok(next) = input.next() else {
Expand All @@ -472,23 +473,32 @@ fn parse_qualified_rule(input: &mut StepParser, ss: &mut StyleSheetTransformer)
ss.add_warning(error::ParseErrorKind::HostSelectorCombination, pos..pos);
} else {
ss.write_in_low_priority(input, |ss, input| {
ss.append_token(
StepToken::wrap_at(Token::SquareBracketBlock, &next),
input,
None,
);
ss.append_token(
StepToken::wrap_at(Token::Ident("is".into()), &next),
input,
None,
);
ss.append_token(StepToken::wrap_at(Token::Delim('='), &next), input, None);
ss.append_token(StepToken::wrap_at(quoted_host_is, &next), input, None);
ss.append_token(
StepToken::wrap_at(Token::CloseSquareBracket, &next),
input,
None,
);
let write_attr_selector = |ss: &mut StyleSheetTransformer, input: &mut StepParser, name: &str, value: &str| {
ss.append_token(
StepToken::wrap_at(Token::SquareBracketBlock, &next),
input,
None,
);
ss.append_token(
StepToken::wrap_at(Token::Ident(name.into()), &next),
input,
None,
);
ss.append_token(StepToken::wrap_at(Token::Delim('='), &next), input, None);
let quoted_value = Token::QuotedString(value.into());
ss.append_token(StepToken::wrap_at(quoted_value, &next), input, None);
ss.append_token(
StepToken::wrap_at(Token::CloseSquareBracket, &next),
input,
None,
);
};
let p = ss.options.class_prefix.clone().unwrap_or_default();
write_attr_selector(ss, input, "wx-host", &p);
if let Some(host_is) = ss.options.host_is.clone() {
ss.append_token(StepToken::wrap_at(Token::Comma, &next), input, None);
write_attr_selector(ss, input, "is", &host_is);
}
let close = ss.append_nested_block(next, input);
convert_rpx_in_block(input, ss, None);
ss.append_nested_block_close(close, input);
Expand Down Expand Up @@ -902,7 +912,8 @@ mod test {
}
"#,
StyleSheetOptions {
host_is: Some("TEST".to_string()),
class_prefix: Some("ABC".into()),
convert_host: true,
..Default::default()
},
);
Expand All @@ -914,7 +925,7 @@ mod test {
lp.write(&mut s).unwrap();
assert_eq!(
std::str::from_utf8(&s).unwrap(),
r#"[is="TEST"]{color:red;}"#
r#"[wx-host="ABC"]{color:red;}"#
);
}

Expand All @@ -932,7 +943,7 @@ mod test {
.a { color: green }
"#,
StyleSheetOptions {
host_is: Some("TEST".to_string()),
convert_host: true,
..Default::default()
},
);
Expand Down Expand Up @@ -972,7 +983,9 @@ mod test {
}
"#,
StyleSheetOptions {
host_is: Some("/\"\\".to_string()),
class_prefix: None,
convert_host: true,
host_is: Some("IS".into()),
..Default::default()
},
);
Expand All @@ -987,7 +1000,7 @@ mod test {
lp.write(&mut s).unwrap();
assert_eq!(
std::str::from_utf8(&s).unwrap(),
r#"@media(width: 1px){@supports(color: red){[is="/\"\\"]{color:pink;}}}"#
r#"@media(width: 1px){@supports(color: red){[wx-host=""],[is="IS"]{color:pink;}}}"#
);
}

Expand Down
5 changes: 5 additions & 0 deletions glass-easel-stylesheet-compiler/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ struct CmdArgs {

/// Convert `:host` into an attribute selector
#[arg(long)]
convert_host: bool,

/// Convert `:host` into an `is` attribute selector with the specified value ( `--convert-host` must be specified)
#[arg(long)]
host_is: Option<String>,
}

Expand All @@ -59,6 +63,7 @@ fn main() {
class_prefix_sign: args.class_prefix_sign.clone(),
rpx_ratio: args.rpx_ratio,
import_sign: args.import_sign.clone(),
convert_host: args.convert_host,
host_is: args.host_is.clone(),
};

Expand Down
14 changes: 12 additions & 2 deletions glass-easel/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,11 @@ export class Component<
comp._$methodCaller = comp
comp._$virtual = virtualHost

// check shared style scope
const ownerSpace = behavior.ownerSpace
const sharedStyleScope = ownerSpace._$sharedStyleScope
const isDedicatedStyleScope = options.styleScope && options.styleScope !== sharedStyleScope

// create backend element
let backendElement: GeneralBackendElement | null = null
if (nodeTreeContext) {
Expand All @@ -557,7 +562,12 @@ export class Component<
backendElement = (nodeTreeContext as domlikeBackend.Context).document.createElement(
tagName,
)
backendElement.setAttribute('is', def.is)
if (isDedicatedStyleScope) {
backendElement.setAttribute(
'wx-host',
ownerSpace.styleScopeManager.queryName(options.styleScope!),
)
}
if (ENV.DEV) performanceMeasureEnd()
}
} else if (BM.COMPOSED || (BM.DYNAMIC && nodeTreeContext.mode === BackendMode.Composed)) {
Expand Down Expand Up @@ -613,7 +623,7 @@ export class Component<
;(backendElement as composedBackend.Element).setStyleScope(
styleScope,
extraStyleScope,
options.styleScope ?? StyleScopeManager.globalScope(),
isDedicatedStyleScope ? options.styleScope ?? StyleScopeManager.globalScope() : undefined,
)
}
if (styleScopeManager && writeExtraInfoToAttr) {
Expand Down
13 changes: 12 additions & 1 deletion glass-easel/src/component_space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type GeneralBehavior,
type NativeNodeDefinition,
} from './behavior'
import { StyleScopeManager } from './class_list'
import { type StyleScopeId, StyleScopeManager } from './class_list'
import {
Component,
type ComponentDefinition,
Expand Down Expand Up @@ -161,6 +161,8 @@ export class ComponentSpace {
| null = null
/** @internal */
_$allowUnusedNativeNode = true
/** @internal */
_$sharedStyleScope = 0

/**
* Create a new component space
Expand Down Expand Up @@ -201,6 +203,15 @@ export class ComponentSpace {
return this._$componentOptions
}

/**
* Mark a style scope as shared
*
* This style scope will not be written to backend as a dedicated style scope for a component.
*/
setSharedStyleScope(styleScopeId: StyleScopeId) {
this._$sharedStyleScope = styleScopeId
}

/**
* Set (or update) a global using component item
*
Expand Down
8 changes: 6 additions & 2 deletions glass-easel/tests/base/composed_backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,12 @@ abstract class Node implements glassEasel.composedBackend.Element {
})
const tagName = this.tagName.toLowerCase()
ret.push(`<${tagName}`)
const is = this.__wxElement?.asGeneralComponent()?.is
if (typeof is === 'string') props.is = is
if (this._$hostStyleScope !== undefined) {
const def = this.__wxElement?.asGeneralComponent()!.getComponentDefinition()
props['wx-host'] = def!.behavior.ownerSpace.styleScopeManager.queryName(
this._$hostStyleScope,
)!
}
if (this.id) props.id = this.getAttribute('id')!
if (this._$style) props.style = this.getAttribute('style')!
if (this._$classes) props.class = this.getAttribute('class')!
Expand Down
1 change: 1 addition & 0 deletions glass-easel/tests/base/env.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-new-func */
/* eslint-disable @typescript-eslint/no-implied-eval */

// eslint-disable-next-line import/no-extraneous-dependencies
import { TmplGroup } from 'glass-easel-template-compiler'
import * as glassEasel from '../../src'
import * as ComposedBackend from './composed_backend'
Expand Down
10 changes: 8 additions & 2 deletions glass-easel/tests/base/shadow_backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,14 @@ abstract class Node implements glassEasel.backend.Element {
})
const tagName = this.tagName.toLowerCase()
ret.push(`<${tagName}`)
const is = this.__wxElement?.asGeneralComponent()?.is
if (typeof is === 'string') props.is = is
const comp = this.__wxElement?.asGeneralComponent()
if (comp) {
const def = comp.getComponentDefinition()
const scope = def.getComponentOptions().styleScope
if (scope && scope !== def.behavior.ownerSpace._$sharedStyleScope) {
props['wx-host'] = def.behavior.ownerSpace.styleScopeManager.queryName(scope)!
}
}
if (this.id) props.id = this.getAttribute('id')!
if (this._$style) props.style = this.getAttribute('style')!
if (this._$classes) props.class = this.getAttribute('class')!
Expand Down
6 changes: 2 additions & 4 deletions glass-easel/tests/core/behavior.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ describe('chaining-form interface', () => {
)
.registerComponent()
const elem = glassEasel.Component.createWithContext('root', compDef, domBackend)
expect(domHtml(elem)).toBe('<child is=""><div class="invalid a b"></div></child>')
expect(domHtml(elem)).toBe('<child><div class="invalid a b"></div></child>')
})

test('chaining data and observers', () => {
Expand Down Expand Up @@ -579,9 +579,7 @@ describe('chaining-form interface', () => {
const comp = glassEasel.Component.createWithContext('root', compDef, domBackend)
glassEasel.Element.pretendAttached(comp)
expect(eventArr).toStrictEqual([1, 2, 1, 2, 1, 2])
expect(domHtml(comp)).toBe(
'<parent is="parent-comp"><child is="child-comp">A</child><child is="child-comp">B</child><child is="child-comp">C</child></parent>',
)
expect(domHtml(comp)).toBe('<parent><child>A</child><child>B</child><child>C</child></parent>')
})

test('chaining filter', () => {
Expand Down
26 changes: 26 additions & 0 deletions glass-easel/tests/core/component_space.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,32 @@ describe('Component Space', () => {
expect(mainCs.getComponentByUrlWithoutDefault('space://comp', '')).toBe(extraComp)
})

test('shared style scope applied to components', () => {
const cs = new glassEasel.ComponentSpace()
const sharedStyleScope = cs.styleScopeManager.register('aBc')
const styleScope = cs.styleScopeManager.register('aBc')
cs.setSharedStyleScope(sharedStyleScope)

const compDef = cs.defineComponent({
options: {
styleScope: sharedStyleScope,
},
})
const elem = glassEasel.Component.createWithContext('root', compDef, domBackend)
const domElem = elem.$$ as unknown as HTMLElement
expect(domElem.getAttribute('wx-host')).toBe(null)

const compDef2 = cs.defineComponent({
options: {
styleScope,
extraStyleScope: sharedStyleScope,
},
})
const elem2 = glassEasel.Component.createWithContext('root', compDef2, domBackend)
const domElem2 = elem2.$$ as unknown as HTMLElement
expect(domElem2.getAttribute('wx-host')).toBe('aBc')
})

test('create component with URL params (without generics)', () => {
const cs = new glassEasel.ComponentSpace()
const compDef = cs.defineComponent({
Expand Down
4 changes: 2 additions & 2 deletions glass-easel/tests/core/data_update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,15 @@ describe('partial update', () => {
})
const elem = glassEasel.Component.createWithContext('root', def, domBackend)
glassEasel.Element.pretendAttached(elem)
expect(domHtml(elem)).toBe('<x-c is="">10</x-c><x-c is="">20</x-c>')
expect(domHtml(elem)).toBe('<x-c>10</x-c><x-c>20</x-c>')
expect(execArr).toStrictEqual(['B', 'A', 'B', 'A'])
execArr = []
;(elem.data.list[0]!.v as any) = 30
elem.setData({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
list: elem.data.list as any,
})
expect(domHtml(elem)).toBe('<x-c is="">30</x-c><x-c is="">20</x-c>')
expect(domHtml(elem)).toBe('<x-c>30</x-c><x-c>20</x-c>')
expect(execArr).toStrictEqual(['D', 'B', 'A', 'B'])
execArr = []
elem.setData({ list: [] })
Expand Down
Loading

0 comments on commit 4b64562

Please sign in to comment.