Skip to content

Commit

Permalink
feat: add adapter
Browse files Browse the repository at this point in the history
  • Loading branch information
nonzzz committed Jun 26, 2024
1 parent 84cc455 commit 0dcecce
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 10 deletions.
2 changes: 2 additions & 0 deletions examples/waku-demo/src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { create, props } from '@stylexjs/stylex'
import './init.css'
import { ClientBanner } from './client-banner.js'
import { ServerBanner } from './server-banner.js'

const styles = create({
title: {
Expand All @@ -17,6 +18,7 @@ const App = ({ name }: { name: string }) => {
<h1 {...props(styles.title)}>Hello {name}</h1>
<h3>This is a server component.</h3>
<ClientBanner />
<ServerBanner />
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/waku-demo/src/components/client-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { create, props } from '@stylexjs/stylex'

const styles = create({
root: {
backgroundColor: '#444',
backgroundColor: 'red',
color: '#fff',
padding: '10px',
textAlign: 'center'
Expand Down
16 changes: 16 additions & 0 deletions examples/waku-demo/src/components/server-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict'

import { create, props } from '@stylexjs/stylex'

const styles = create({
root: {
backgroundColor: 'purple',
color: '#fff',
padding: '20px',
textAlign: 'center'
}
})

export const ServerBanner = () => {
return <div {...props(styles.root)}>This is a server banner by StyleX CSS</div>
}
4 changes: 2 additions & 2 deletions examples/waku-demo/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { defineConfig } from 'vite'

import { stylex } from 'vite-plugin-stylex-dev'
import { waku } from 'vite-plugin-stylex-dev/adapter'

export default defineConfig({
ssr: {
external: ['@stylexjs/stylex']
},
plugins: [stylex()]
plugins: [stylex({ adapter: waku })]
})
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
"./client": {
"types": "./client.d.ts"
},
"./adapter": {
"types": "./dist/adapter/index.d.ts",
"import": "./dist/adapter/index.mjs",
"require": "./dist/adapter/index.js"
},
"./*": "./*"
},
"files": [
Expand Down
17 changes: 16 additions & 1 deletion rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,27 @@ export default defineConfig([
],
plugins: [
swc()
// minify({ mangle: true, module: true, compress: true, sourceMap: true })
]
},
{
input: 'src/index.ts',
output: { file: 'dist/index.d.ts' },
plugins: [dts({})]
},
{
input: 'src/adapter/index.ts',
external,
output: [
{ file: 'dist/adapter/index.mjs', format: 'esm', exports: 'named' },
{ file: 'dist/adapter/index.js', format: 'cjs', exports: 'named' }
],
plugins: [
swc()
]
},
{
input: 'src/adapter/index.ts',
output: { file: 'dist/adapter/index.d.ts' },
plugins: [dts({})]
}
])
1 change: 1 addition & 0 deletions src/adapter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { waku } from './waku'
101 changes: 101 additions & 0 deletions src/adapter/waku.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Plugin, ViteDevServer } from 'vite'
import { Rule } from '@stylexjs/babel-plugin'
import type { AdapterConfig } from '../interface'
import { CONSTANTS } from '../plugins/server'
import { parseRequest } from '../context'

const STYLEX_FOR_WAKU_MARKER = '/__stylex__waku.css'
/** */

const hmr = (css: string) => `
import { createHotContext as __vite__createHotContext } from "/@vite/client"
import.meta.hot = __vite__createHotContext("/__stylex__waku.css")
import { updateStyle as __vite__updateStyle, removeStyle as __vite__removeStyle } from "/@vite/client"
const __vite__id = "/__stylex__waku.css"
const __vite__css = ${JSON.stringify(css)}
__vite__updateStyle(__vite__id, __vite__css)
import.meta.hot.accept()
import.meta.hot.prune(() => __vite__removeStyle(__vite__id))
`
const sharedRules = new Map<string, Rule[]>()

export function waku() {
return <AdapterConfig> {
name: 'waku',
setup(ctx, plugin) {
if (ctx.env === 'build') return
// @ts-expect-error
ctx.vite.config.plugins = ctx.vite.config.plugins.filter(p => p.name !== 'stylex:server')

let viteDevServer: ViteDevServer | null = null
const entries: Set<string> = new Set()
const effects: Set<string> = new Set()

const invalidate = (ids: Set<string>) => {
for (const id of ids) {
const mod = viteDevServer?.moduleGraph.getModuleById(id)
if (!mod) continue
viteDevServer?.moduleGraph.invalidateModule(mod)
}
}

const self = <Partial<Plugin>> {
configureServer(server) {
viteDevServer = server

viteDevServer.watcher.on('unlink', (path) => {
const { original } = parseRequest(path)
if (sharedRules.has(original)) {
sharedRules.delete(original)
// update hmr
}
})

viteDevServer.middlewares.use((req, res, next) => {
const protocol = 'encrypted' in (req?.socket ?? {}) ? 'https' : 'http'
const { host } = req.headers
// @ts-ignore
const url = new URL(req.originalUrl, `${protocol}://${host}`)
if (url.pathname === STYLEX_FOR_WAKU_MARKER) {
res.writeHead(200, {
'Content-Type': 'application/javascript',
'x-powered-by': 'vite-plugin-stylex-dev'
})
res.end(hmr(ctx.produceCSS(sharedRules)))
return
}
next()
})
},
load(id) {
if (id === STYLEX_FOR_WAKU_MARKER) {
return {
code: '',
map: { mappings: '' }
}
}
},
resolveId(id) {
if (id === CONSTANTS.VIRTUAL_STYLEX_ID) {
entries.add(STYLEX_FOR_WAKU_MARKER)
return STYLEX_FOR_WAKU_MARKER
}
},
async transform(code, id, opt) {
const result = await ctx.transform?.apply(this, [code, id, opt])
const { original } = parseRequest(id)
if (ctx.rules.has(original)) {
effects.add(original)
sharedRules.set(original, ctx.rules.get(original)!)
invalidate(entries)
}
return result
}
}

Object.assign(plugin, self)
}
}
}
6 changes: 3 additions & 3 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@ export class PluginContext {
return code
}

produceCSS() {
if (!this.styleRules.size) return ''
produceCSS(input = this.styleRules) {
if (!input.size) return ''
const { useCSSLayers } = this.stylexOptions
return stylexBabelPlugin.processStylexRules([...this.styleRules.values()].flat().filter(Boolean), useCSSLayers!) + '\n' +
return stylexBabelPlugin.processStylexRules([...input.values()].flat().filter(Boolean), useCSSLayers!) + '\n' +
Object.values(this.globalStyles).join('\n')
}

Expand Down
26 changes: 24 additions & 2 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import type { FilterPattern, HookHandler, Plugin } from 'vite'
import type { Options } from '@stylexjs/babel-plugin'
import type { FilterPattern, HookHandler, Plugin, ResolvedConfig } from 'vite'
import type { Options, Rule } from '@stylexjs/babel-plugin'
import type { PluginItem } from '@babel/core'
import type { StylexExtendBabelPluginOptions } from '@stylex-extend/babel-plugin'
import { noop } from './shared'
import type { Env, PluginContext } from './context'

export type Mutable<T> = {
-readonly [P in keyof T]: T[P]
}

interface AdapterViteOptions {
cssPlugins: Plugin[]
config: ResolvedConfig
}
export interface AdapterContext {
env: Env
vite: AdapterViteOptions
rules: PluginContext['styleRules']
produceCSS: (input?: Map<string, Rule[]>) => string
transform: HookHandler<Plugin['transform']>
}

export interface AdapterConfig {
name: string
setup: (ctx: AdapterContext, plugin: Plugin) => void
}

export type Pretty<T> =
& {
[key in keyof T]: T[key] extends (...args: any[]) => any ? (...args: Parameters<T[key]>) => ReturnType<T[key]>
Expand Down Expand Up @@ -46,6 +64,10 @@ interface InternalStylexPluginOptions extends Partial<InternalOptions> {
* @experimental
*/
enableStylexExtend?: boolean | StylexExtendOptions
/**
* @experimental
*/
adapter?: () => AdapterConfig
[prop: string]: unknown
}

Expand Down
20 changes: 19 additions & 1 deletion src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import type { Plugin } from 'vite'
import { PluginContext } from '../context'
import { searchForPackageRoot, unique } from '../shared'
import { error, searchForPackageRoot, unique } from '../shared'
import type { AdapterContext } from '../interface'
import { stylexBuild } from './build'
import { CONSTANTS, stylexServer } from './server'

export function createForViteServer(ctx: PluginContext, extend: (c: PluginContext) => Plugin) {
const cssPlugins: Plugin[] = []

return (plugin: Plugin) => {
plugin.configResolved = function configResolved(conf) {
const adapterContext: AdapterContext = {
// eslint-disable-next-line prefer-spread
produceCSS: (...rest: any) => ctx.produceCSS.apply(ctx, rest),
transform: plugin.transform as any,
vite: { cssPlugins, config: conf },
env: ctx.env,
rules: ctx.styleRules
}

ctx.env = conf.command === 'serve' ? 'server' : 'build'
ctx.root = searchForPackageRoot(conf.root)
const { importSources, stylexOptions } = ctx
Expand Down Expand Up @@ -37,6 +48,13 @@ export function createForViteServer(ctx: PluginContext, extend: (c: PluginContex
conf.plugins.splice(pos, 0, extend(ctx))
}
ctx.env === 'build' ? stylexBuild(plugin, ctx, cssPlugins) : stylexServer(plugin, ctx, cssPlugins, conf)
if (typeof ctx.stylexOptions.adapter === 'function') {
const adapter = ctx.stylexOptions.adapter()
if (!adapter.name) {
throw error('adapter missing name.')
}
adapter.setup(adapterContext, plugin)
}
}
}
}

0 comments on commit 0dcecce

Please sign in to comment.