Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vite, webpack): add extraContent option #1963

Merged
merged 4 commits into from Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions examples/astro-vue/.gitignore
@@ -0,0 +1,20 @@
# build output
dist/
.output/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store
10 changes: 10 additions & 0 deletions examples/astro-vue/astro.config.ts
@@ -0,0 +1,10 @@
import { defineConfig } from 'astro/config'
import UnoCSS from 'unocss/astro'
import vue from '@astrojs/vue'

export default defineConfig({
integrations: [
vue(),
UnoCSS(),
],
})
19 changes: 19 additions & 0 deletions examples/astro-vue/package.json
@@ -0,0 +1,19 @@
{
"name": "unocss-astro",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"vue": "^3.2.30"
},
"devDependencies": {
"@astrojs/vue": "^1.2.1",
"astro": "^1.6.11",
"unocss": "link:../../packages/unocss"
}
}
Empty file.
21 changes: 21 additions & 0 deletions examples/astro-vue/src/components/Card.astro
@@ -0,0 +1,21 @@
---
export interface Props {
title: string;
body: string;
href: string;
}

const { href, title, body } = Astro.props as Props;
---

<li class="border rounded inline-block px4 py2 m2 hover:shadow">
<a href={href}>
<h2 class="font-bold">
{title}
<span>&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
11 changes: 11 additions & 0 deletions examples/astro-vue/src/components/Example.vue
@@ -0,0 +1,11 @@
<template>
<div class=":uno: text-(xl red-400) mt-8">
Example Vue component
</div>
</template>

<style scoped>
div {
@apply mt-8;
}
</style>
1 change: 1 addition & 0 deletions examples/astro-vue/src/env.d.ts
@@ -0,0 +1 @@
/// <reference types="astro/client" />
19 changes: 19 additions & 0 deletions examples/astro-vue/src/layouts/Layout.astro
@@ -0,0 +1,19 @@
---
export interface Props {
title: string;
}

const { title } = Astro.props as Props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body class="font-sans text-center">
<slot />
</body>
</html>
15 changes: 15 additions & 0 deletions examples/astro-vue/src/pages/foo.astro
@@ -0,0 +1,15 @@
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
---

<Layout title="Foo Page">
<div class="text-center text-lg mt-10">Foo</div>
<ul role="list" class="link-card-grid">
<Card
href="/"
title="Home"
body="Go home."
/>
</ul>
</Layout>
25 changes: 25 additions & 0 deletions examples/astro-vue/src/pages/index.astro
@@ -0,0 +1,25 @@
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
import Example from '../components/Example.vue';
---

<Layout title="Welcome to Astro.">
<main class="p10 text-center">
<div>
<div class="i-logo"></div>
</div>
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
<p class="instructions">
Check out the <code>src/pages</code> directory to get started.<br />
</p>
<ul role="list" class="link-card-grid">
<Card
href="/foo"
title="/foo"
body="Go to page foo."
/>
<Example client:only="vue" />
</ul>
</main>
</Layout>
10 changes: 10 additions & 0 deletions examples/astro-vue/tsconfig.json
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
}
}
28 changes: 28 additions & 0 deletions examples/astro-vue/uno.config.ts
@@ -0,0 +1,28 @@
import {
defineConfig,
presetIcons,
presetUno,
transformerCompileClass,
transformerDirectives,
transformerVariantGroup,
} from 'unocss'

export default defineConfig({
shortcuts: [
{ 'i-logo': 'i-logos-astro w-6em h-6em transform transition-800' },
],
transformers: [
transformerDirectives(),
transformerCompileClass(),
transformerVariantGroup(),
],
presets: [
presetUno(),
presetIcons({
extraProperties: {
'display': 'inline-block',
'vertical-align': 'middle',
},
}),
],
})
7 changes: 7 additions & 0 deletions packages/astro/src/index.ts
@@ -1,3 +1,5 @@
import { resolve } from 'path'
import { fileURLToPath } from 'url'
import type { AstroIntegration } from 'astro'
import type { VitePluginConfig } from '@unocss/vite'
import VitePlugin from '@unocss/vite'
Expand Down Expand Up @@ -38,6 +40,11 @@ export default function UnoCSSAstroIntegration<Theme extends {}>(
name: 'unocss',
hooks: {
'astro:config:setup': async ({ config, injectScript }) => {
// Adding components to UnoCSS's extra content
options.extraContent ||= {}
options.extraContent.filesystem ||= []
options.extraContent.filesystem.push(resolve(fileURLToPath(config.root), 'src/components/**/*').replace(/\\/g, '/'))

config.vite.plugins ||= []
config.vite.plugins.push(...VitePlugin(options, defaults) as any)

Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/types.ts
Expand Up @@ -531,6 +531,13 @@ export interface UnocssPluginContext<Config extends UserConfig = UserConfig> {
/** Module IDs that been affected by UnoCSS */
affectedModules: Set<string>

/** Pending promises */
tasks: Promise<any>[]
/**
* Await all pending tasks
*/
flushTasks(): Promise<any>

filter: (code: string, id: string) => boolean
extract: (code: string, id?: string) => Promise<void>

Expand Down Expand Up @@ -581,6 +588,19 @@ export interface SourceCodeTransformer {
transform: (code: MagicString, id: string, ctx: UnocssPluginContext) => Awaitable<void>
}

export interface ExtraContentOptions {
/**
* Glob patterns to match the files to be extracted
* In dev mode, the files will be watched and trigger HMR
*/
filesystem?: string[]

/**
* Plain text to be extracted
*/
plain?: string[]
}

/**
* For other modules to aggregate the options
*/
Expand Down Expand Up @@ -611,6 +631,11 @@ export interface PluginOptions {
* Custom transformers to the source code
*/
transformers?: SourceCodeTransformer[]

/**
* Extra content outside of build pipeline (assets, backend, etc.) to be extracted
*/
extraContent?: ExtraContentOptions
}

export interface UserConfig<Theme extends {} = {}> extends ConfigBase<Theme>, UserOnlyOptions<Theme>, GeneratorOptions, PluginOptions, CliOptions {}
Expand Down
11 changes: 10 additions & 1 deletion packages/shared-integration/src/context.ts
Expand Up @@ -23,6 +23,7 @@ export function createContext<Config extends UserConfig<any> = UserConfig<any>>(

const modules = new BetterMap<string, string>()
const tokens = new Set<string>()
const tasks: Promise<void>[] = []
const affectedModules = new Set<string>()

let ready = reloadConfig()
Expand Down Expand Up @@ -83,7 +84,7 @@ export function createContext<Config extends UserConfig<any> = UserConfig<any>>(
invalidate()
}

const filter = (code: string, id: string) => {
function filter(code: string, id: string) {
if (code.includes(IGNORE_COMMENT))
return false
return code.includes(INCLUDE_COMMENT) || code.includes(CSS_PLACEHOLDER) || rollupFilter(id.replace(/\?v=\w+$/, ''))
Expand All @@ -94,13 +95,21 @@ export function createContext<Config extends UserConfig<any> = UserConfig<any>>(
return rawConfig
}

async function flushTasks() {
const _tasks = [...tasks]
await Promise.all(_tasks)
tasks.splice(0, _tasks.length)
}

return {
get ready() {
return ready
},
tokens,
modules,
affectedModules,
tasks,
flushTasks,
invalidate,
onInvalidate(fn: () => void) {
invalidations.push(fn)
Expand Down
55 changes: 55 additions & 0 deletions packages/shared-integration/src/extra-content.ts
@@ -0,0 +1,55 @@
import fs from 'fs/promises'
import { resolve } from 'path'
import fg from 'fast-glob'
import type { UnocssPluginContext } from '@unocss/core'
import { applyTransformers } from './transformers'

export async function setupExtraContent(ctx: UnocssPluginContext, shouldWatch = false) {
const { extraContent } = await ctx.getConfig()
const { extract, tasks, root, filter } = ctx

// plain text
if (extraContent?.plain) {
await Promise.all(
extraContent.plain.map((code, idx) => {
return extract(code, `__extra_content_${idx}__`)
}),
)
}

// filesystem
if (extraContent?.filesystem) {
const files = await fg(extraContent.filesystem, { cwd: root })

async function extractFile(file: string) {
const code = await fs.readFile(file, 'utf-8')
if (!filter(code, file))
return
const preTransform = await applyTransformers(ctx, code, file, 'pre')
const defaultTransform = await applyTransformers(ctx, preTransform?.code || code, file)
await applyTransformers(ctx, defaultTransform?.code || preTransform?.code || code, file, 'post')
return await extract(preTransform?.code || code, file)
}

if (shouldWatch) {
const { watch } = await import('chokidar')
const ignored = ['**/{.git,node_modules}/**']

const watcher = watch(files, {
ignorePermissionErrors: true,
ignored,
cwd: root,
ignoreInitial: true,
})

watcher.on('all', (type, file) => {
if (type === 'add' || type === 'change') {
const absolutePath = resolve(root, file)
tasks.push(extractFile(absolutePath))
}
})
}

await Promise.all(files.map(extractFile))
}
}
2 changes: 2 additions & 0 deletions packages/vite/package.json
Expand Up @@ -53,6 +53,8 @@
"@unocss/inspector": "workspace:*",
"@unocss/scope": "workspace:*",
"@unocss/transformer-directives": "workspace:*",
"chokidar": "^3.5.3",
"fast-glob": "^3.2.12",
"magic-string": "^0.27.0"
},
"devDependencies": {
Expand Down