Replies: 2 comments 9 replies
-
Hi @bokerman, we don't officially support React 19 yet, so we cannot guarantee that everything will work out of the box. My suggestion would be to start with a lower version of React to ensure you are in a supported version of the library. |
Beta Was this translation helpful? Give feedback.
-
You could implement a Not directly related to React, but I spent the past few days writing a Vite plugin to wrap FluenUI Web Components into SolidJS components and generate TypeScript declaration: import { normalizePath, Plugin } from 'vite'
import { resolve } from 'path'
import { readFile, writeFile } from 'fs/promises'
import { createSourceFile, EntityName, isExportDeclaration, isIdentifier as isTsIdentifier, isNamedExports, isTypeQueryNode, isTypeReferenceNode, isVariableStatement, NodeArray, ScriptTarget, SyntaxKind, TypeNode } from 'typescript'
import { parse, transformAsync, traverse } from '@babel/core'
import { generate } from '@babel/generator'
import { Identifier, ImportSpecifier, isIdentifier as isJsIdentifier, isMemberExpression, isStringLiteral, isTemplateLiteral } from '@babel/types'
export default function SolidFluent(options?: {
/** default `src/solid-fluent.d.ts` */
types?: string
}) {
const cache = (async () => {
const { version, types } = JSON.parse(await readFile(resolve(__dirname, 'node_modules/@fluentui/web-components/package.json'), 'utf8')) as {
version: string
types: string
}
try {
const declaration = resolve(__dirname, 'node_modules/@fluentui/web-components', types),
statements = createSourceFile(declaration, await readFile(declaration, 'utf8'), ScriptTarget.Latest).statements,
aliases = Object.fromEntries(statements.filter(s => isExportDeclaration(s)).map(s => s.exportClause)
.flatMap(e => e && isNamedExports(e) ? e.elements : [])
.map(({ name, propertyName }) => propertyName && isTsIdentifier(name) && isTsIdentifier(propertyName) &&
[propertyName.escapedText.toString(), name.escapedText.toString()]
).filter(e => e) as [string, string][]
)
return [version, Object.fromEntries(statements.flatMap(s => isVariableStatement(s) &&
s.modifiers?.some(m => m.kind === SyntaxKind.ExportKeyword) ? s.declarationList.declarations : []
).map((
{ name, type }, _i, _a,
typeArguments?: NodeArray<TypeNode>, argument?: TypeNode, exprName?: EntityName, expr?: string
) =>
type && isTsIdentifier(name) && isTypeReferenceNode(type) &&
(typeArguments = type.typeArguments) && isTsIdentifier(type.typeName) &&
isTypeQueryNode(argument = typeArguments[0]) && isTsIdentifier(exprName = argument.exprName) &&
[name.escapedText.toString(), aliases[expr = exprName.escapedText.toString()] || expr]
).filter(e => e) as [string, string][])]
} catch (e) {
if (typeof version !== 'string' || !(parseInt(version.split('.', 1)[0]) > 2)) {
console.error('@fluentui/web-components version must be at least 3')
}
throw e
}
})(), id = 'virtual:solid-fluent', rid = '\0' + id, esmp = normalizePath(resolve(__dirname, 'node_modules/@fluentui/web-components/dist/esm'))
let dev: boolean
return {
name: 'solid-fluent',
enforce: 'pre',
configResolved(config) {
dev = config.command === 'serve'
},
async buildStart() {
const [version, components] = await cache, d = `declare module 'virtual:solid-fluent' {
import * as F from '@fluentui/web-components'
import { JSX } from 'solid-js'
type SolidFluentProps<T> = JSX.HTMLAttributes<T> & {
[K in Exclude<keyof T, keyof HTMLElement> & {
[P in keyof T]: T[P] extends (...args: any[]) => any ? never : P
}[keyof T]]?: T[K]
}
function c<T>(props: SolidFluentProps<T>): T
const ${Object.values(components).map(c => `${c} = c<F.${c}>`).join(', ')}
}`, t = options?.types || 'src/solid-fluent.d.ts', p = resolve(__dirname, t), b = performance.now()
let skip = false
try {
skip = await readFile(p, 'utf8') === d
} catch { }
if (!skip) {
console.log(`Generating \x1b[35m${t}\x1b[0m for \x1b[36m@fluentui/web-components@${version}\x1b[0m...`)
await writeFile(p, d)
}
console.log(`\x1b[32m✓ \x1b[35m${t}\x1b[${skip ? '0m already up-to-date.' : `32m generated in ${(performance.now() - b).toFixed(2)}ms`}\x1b[0m`)
},
resolveId(source) {
if (source === id) {
return rid
}
},
async load(id) {
if (id === rid) {
const [, components] = await cache
return `import * as F from '@fluentui/web-components'
import { spread } from 'solid-js/web'
${Object.keys(components).map(d => `F.${d}.define(F.FluentDesignSystem.registry)`).join('\n')}
const c = t => p => (e => (spread(e, p), e))(new t())
export const ${Object.values(components).map(c => `${c} = c(F.${c})`).join(', ')}`
}
},
async transform(code, id) {
if (id === rid) {
return await transformAsync(code, {
presets: ['babel-preset-solid'],
compact: true
})
} else if (!dev && id.startsWith(esmp)) {
const ast = parse(code, { sourceType: 'module' })
if (ast) {
let html = new Set<string>(), css = new Set<string>(), any = false
function stripCss(raw: string) {
const c = raw.replace(/\n\s*|(?<=[\s(){}])\s+|\s+(?=[(){}])|\s*\n|;?\s*(?=})|(?<=:)\s+(?=[^"']+;)/g, '')
if (c !== raw) {
any = true
}
return c
}
traverse(ast, {
ImportDeclaration({ node: { source, specifiers } }) {
if (source.value === '@microsoft/fast-element') {
for (const s of specifiers) {
({ html, css })[((s as ImportSpecifier).imported as Identifier)?.name]?.add(s.local.name)
}
}
},
TaggedTemplateExpression({ node: { tag, quasi } }) {
const i = isJsIdentifier(tag)
if (i && html.has(tag.name) ||
isMemberExpression(tag) && tag.object && isJsIdentifier(tag.object) && tag.object.name &&
html.has(tag.object.name) && isJsIdentifier(tag.property) && tag.property.name === 'partial') {
for (const { value } of quasi.quasis) {
const original = value.raw
if (original !== (value.raw = original.replace(/\n\s*|\s+|\s*\n/g, ' '))) {
any = true
}
}
} else if (i && css.has(tag.name) ||
isMemberExpression(tag) && tag.object && isJsIdentifier(tag.object) && tag.object.name &&
css.has(tag.object.name) && isJsIdentifier(tag.property) && tag.property.name === 'partial') {
for (const { value } of quasi.quasis) {
value.raw = stripCss(value.raw)
}
}
},
CallExpression({ node: { callee, arguments: [arg] } }) {
if (arg) {
if (isJsIdentifier(callee) && html.has(callee.name) ||
isMemberExpression(callee) && isJsIdentifier(callee.property) && callee.property.name === 'partial') {
function strip(raw: string) {
const c = raw.replace(/\s*([(),<=>{}]|\/>)\s*/g, '$1').replace(/\s\s+/g, ' ')
if (c !== raw) {
any = true
}
return c
}
if (isStringLiteral(arg)) {
arg.value = strip(arg.value)
} else if (isTemplateLiteral(arg)) {
for (const { value } of arg.quasis) {
value.raw = strip(value.raw)
}
}
} else if (isTemplateLiteral(arg) && isMemberExpression(callee) &&
isJsIdentifier(callee.property) && callee.property.name === 'replaceSync') {
for (const { value } of arg.quasis) {
value.raw = stripCss(value.raw)
}
}
}
},
AssignmentExpression({ node }) {
if (isTemplateLiteral(node.right) &&
isMemberExpression(node.left) && isJsIdentifier(node.left.property) && node.left.property.name === 'textContent') {
for (const { value } of node.right.quasis) {
value.raw = stripCss(value.raw)
}
}
}
})
if (any) {
return generate(ast, {}, code)
}
}
}
}
} as Plugin
} Then I can use them as below: import { Button as FluentButton } from 'virtual:solid-fluent'
export default TwoButtons() {
return (<>
<FluentButton>Left</FluentButton>
<FluentButton>Right</FluentButton>
</>)
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Starting a new React project with Fluent UI, would you recommend starting React 19 (with React Fluent UI components)? All the advices, recommendations and experience is much appreciated.
Beta Was this translation helpful? Give feedback.
All reactions