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: suport definePage #183

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,22 @@ export default defineUniPages({

现在所有的 page 都会被自动发现!

### SFC 自定义块用于路由数据
### 自定义路由数据

#### 1、definePage 定义路由数据
可以在setup中使用 definePage 来定义路由数据。 注意解析definePage是在编译期 不能使用运行期的变量

```ts
import { definePage } from '@uni-helper/vite-plugin-uni-pages'

definePage({
style:{
navigationBarTitleText: 'test'
}
})
```

#### 2、SFC 自定义块用于路由数据
通过添加一个 `<route>` 块到 SFC 中来添加路由元数据。这将会在路由生成后直接添加到路由中,并且会覆盖。

你可以使用 `<route lang="yaml">` 来指定一个解析器,或者使用 `routeBlockLang` 选项来设置一个默认的解析器。
Expand Down
4 changes: 4 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
"vite": "^5.0.0"
},
"dependencies": {
"@babel/generator": "^7.25.5",
"@babel/types": "^7.25.4",
"@uni-helper/uni-env": "^0.1.4",
"@vue-macros/common": "^1.12.2",
"@vue/compiler-sfc": "^3.4.38",
"chokidar": "^3.6.0",
"debug": "^4.3.6",
Expand All @@ -70,6 +73,7 @@
},
"devDependencies": {
"@antfu/utils": "^0.7.10",
"@types/babel__generator": "^7.6.8",
"@types/debug": "^4.1.12",
"@types/lodash.groupby": "^4.6.9",
"@types/node": "^20.15.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export const RESOLVED_MODULE_ID_VIRTUAL = `\0${MODULE_ID_VIRTUAL}`
export const OUTPUT_NAME = 'pages.json'

export const FILE_EXTENSIONS = ['vue', 'nvue', 'uvue']

export const MACRO_DEFINE_PAGE = 'definePage'
37 changes: 12 additions & 25 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import path from 'node:path'
import process from 'node:process'
import fs from 'node:fs'
import type { FSWatcher } from 'chokidar'
import type { Logger, ViteDevServer } from 'vite'
import { normalizePath } from 'vite'
import { loadConfig } from 'unconfig'
import { slash } from '@antfu/utils'
import dbg from 'debug'
Expand All @@ -13,21 +13,15 @@ import type { PagesConfig } from './config/types'
import type { PageMetaDatum, PagePath, ResolvedOptions, SubPageMetaDatum, UserOptions } from './types'
import { writeDeclaration } from './declaration'

import {
debug,
invalidatePagesModule,
isTargetFile,
mergePageMetaDataArray,
useCachedPages,
} from './utils'
import { debug, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, useCachedPages } from './utils'
import { resolveOptions } from './options'
import { checkPagesJsonFile, getPageFiles, readFileSync, writeFileSync } from './files'
import { getRouteBlock, getRouteSfcBlock } from './customBlock'
import { parseSFC } from './customBlock'
import { OUTPUT_NAME } from './constant'

let lsatPagesJson = ''

const { setCache, hasChanged } = useCachedPages()
const { setCache, hasChanged, parseData } = useCachedPages()
export class PageContext {
private _server: ViteDevServer | undefined

Expand Down Expand Up @@ -178,18 +172,11 @@ export class PageContext {

async parsePage(page: PagePath): Promise<PageMetaDatum> {
const { relativePath, absolutePath } = page
const routeSfcBlock = await getRouteSfcBlock(absolutePath)
const routeBlock = await getRouteBlock(absolutePath, routeSfcBlock, this.options)
setCache(absolutePath, routeSfcBlock)
const relativePathWithFileName = relativePath.replace(path.extname(relativePath), '')
const pageMetaDatum: PageMetaDatum = {
path: normalizePath(relativePathWithFileName),
type: routeBlock?.attr.type ?? 'page',
}

if (routeBlock)
Object.assign(pageMetaDatum, routeBlock.content)

const content = fs.readFileSync(absolutePath, 'utf8')
const sfcDescriptor = await parseSFC(content)
const pageMetaDatum = await parseData(relativePathWithFileName, sfcDescriptor, this.options)
setCache(absolutePath, pageMetaDatum)
return pageMetaDatum
}

Expand Down Expand Up @@ -244,9 +231,7 @@ export class PageContext {
}

async mergePageMetaData() {
const pageMetaData = await this.parsePages(this.pagesPath, 'main', this.pagesGlobConfig?.pages)

this.pageMetaData = pageMetaData
this.pageMetaData = await this.parsePages(this.pagesPath, 'main', this.pagesGlobConfig?.pages)
debug.pages(this.pageMetaData)
}

Expand Down Expand Up @@ -279,7 +264,9 @@ export class PageContext {

async updatePagesJSON(filepath?: string) {
if (filepath) {
if (!await hasChanged(filepath)) {
const content = fs.readFileSync(filepath, 'utf8')
const sfcDescriptor = await parseSFC(content)
if (!await hasChanged(filepath, sfcDescriptor, this.options)) {
debug.cache(`The route block on page ${filepath} did not send any changes, skipping`)
return false
}
Expand Down
12 changes: 3 additions & 9 deletions packages/core/src/customBlock.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import fs from 'node:fs'
import JSON5 from 'json5'
import { parse as YAMLParser } from 'yaml'
import { parse as VueParser } from '@vue/compiler-sfc'
import type { SFCBlock, SFCDescriptor } from '@vue/compiler-sfc'
import { parse as VueParser } from '@vue/compiler-sfc'
import { debug } from './utils'
import type { CustomBlock, ResolvedOptions } from './types'

Expand Down Expand Up @@ -72,13 +71,8 @@ export function parseCustomBlock(
}
}

export async function getRouteSfcBlock(path: string): Promise<SFCBlock | undefined> {
const content = fs.readFileSync(path, 'utf8')

const parsedSFC = await parseSFC(content)
const blockStr = parsedSFC?.customBlocks.find(b => b.type === 'route')

return blockStr
export async function getRouteSfcBlock(parsedSFC: SFCDescriptor): Promise<SFCBlock | undefined> {
return parsedSFC?.customBlocks.find(b => b.type === 'route')
}

export async function getRouteBlock(path: string, blockStr: SFCBlock | undefined, options: ResolvedOptions): Promise<CustomBlock | undefined> {
Expand Down
91 changes: 91 additions & 0 deletions packages/core/src/definePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { SFCDescriptor, SFCScriptBlock } from '@vue/compiler-sfc'
import { babelParse, isCallOf } from '@vue-macros/common'
import * as t from '@babel/types'
import generate from '@babel/generator'
import { MACRO_DEFINE_PAGE } from './constant'
import type { PageMetaDatum } from './types'

function findMacroWithImports(scriptSetup: SFCScriptBlock | null) {
try {
const empty = { imports: [], macro: undefined }

if (!scriptSetup)
return empty

const parsed = babelParse(scriptSetup.content, scriptSetup.lang || 'js', {
plugins: [['importAttributes', { deprecatedAssertSyntax: true }]],
})

const stmts = parsed.body

const nodes = stmts
.map((raw: t.Node) => {
let node = raw
if (raw.type === 'ExpressionStatement')
node = raw.expression
return isCallOf(node, MACRO_DEFINE_PAGE) ? node : undefined
})
.filter((node): node is t.CallExpression => !!node)

if (!nodes.length)
return empty

if (nodes.length > 1)
throw new Error(`duplicate ${MACRO_DEFINE_PAGE}() call`)

const macro = nodes[0]

const [arg] = macro.arguments

if (arg && !t.isFunctionExpression(arg) && !t.isArrowFunctionExpression(arg) && !t.isObjectExpression(arg))
throw new Error(`${MACRO_DEFINE_PAGE}() only accept argument in function or object`)

const imports = stmts
.map((node: t.Node) => (node.type === 'ImportDeclaration') ? node : undefined)
.filter((node): node is t.ImportDeclaration => !!node)

return {
imports,
macro,
}
} catch (error) {
throw new Error(`Error parsing script setup content: ${error.message}`)
}
}

export async function readPageOptionsFromMacro(sfc: SFCDescriptor) {
const { macro } = findMacroWithImports(sfc.scriptSetup)

if (!macro)
return {}

if (sfc?.customBlocks.find(b => b.type === 'route'))
throw new Error(`mixed ${MACRO_DEFINE_PAGE}() and <route/> is not allowed`)

const [arg] = macro.arguments

if (!arg)
return {}

let code
if (typeof generate === 'function') {
code = generate(arg).code
}
else {
code = (generate as any).default(arg).code
}

const script = t.isFunctionExpression(arg) || t.isArrowFunctionExpression(arg)
? `return await Promise.resolve((${code})())`
: `return ${code}`

const AsyncFunction = Object.getPrototypeOf(async () => { }).constructor

const fn = new AsyncFunction(script)

return await fn() as Partial<PageMetaDatum>
}
CrazyZhang3 marked this conversation as resolved.
Show resolved Hide resolved

export function findMacro(scriptSetup: SFCScriptBlock | null) {
return findMacroWithImports(scriptSetup).macro
}
9 changes: 9 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Plugin } from 'vite'
import { createLogger } from 'vite'
import MagicString from 'magic-string'
import chokidar from 'chokidar'
import { parse as parseSFC } from '@vue/compiler-sfc'
import type { UserOptions } from './types'
import { PageContext } from './context'
import {
Expand All @@ -13,6 +14,7 @@ import {
RESOLVED_MODULE_ID_VIRTUAL,
} from './constant'
import { checkPagesJsonFile } from './files'
import { findMacro } from './definePage'

export * from './config'
export * from './types'
Expand Down Expand Up @@ -91,6 +93,13 @@ export function VitePluginUniPages(userOptions: UserOptions = {}): Plugin {
s.remove(index, index + length)
}

const { descriptor: sfc } = parseSFC(code, { filename: id })
const macro = findMacro(sfc.scriptSetup)
if (macro) {
const setupOffset = sfc.scriptSetup!.loc.start.offset
s.remove(setupOffset + macro.start!, setupOffset + macro.end!)
}

if (s.hasChanged()) {
return {
code: s.toString(),
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,8 @@ export interface SubPageMetaDatum {
root: string
pages: PageMetaDatum[]
}

export function definePage(_options: Partial<
PageMetaDatum
>) {
}
Comment on lines +144 to +147
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implement the function to handle page metadata.

The function is a placeholder and does not perform any operations. It should be implemented to handle page metadata.

Apply this diff to implement the function:

 export function definePage(_options: Partial<
   PageMetaDatum
 >) {
+  // Implement the function to handle page metadata
+  // Example implementation:
+  const pageMeta: PageMetaDatum = {
+    path: _options.path || '',
+    type: _options.type,
+    style: _options.style,
+    needLogin: _options.needLogin,
+    ..._options,
+  }
+  return pageMeta
 }

Committable suggestion was skipped due to low confidence.

38 changes: 24 additions & 14 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Debug from 'debug'
import { type ModuleNode, type ViteDevServer, normalizePath } from 'vite'
import groupBy from 'lodash.groupby'
import type { SFCBlock } from '@vue/compiler-sfc'
import type { SFCDescriptor } from '@vue/compiler-sfc'
import { FILE_EXTENSIONS, RESOLVED_MODULE_ID_VIRTUAL } from './constant'
import type { PageMetaDatum } from './types'
import { getRouteSfcBlock } from './customBlock'
import type { PageMetaDatum, ResolvedOptions } from './types'
import { getRouteBlock, getRouteSfcBlock } from './customBlock'
import { readPageOptionsFromMacro } from './definePage'

export function invalidatePagesModule(server: ViteDevServer) {
const { moduleGraph } = server
Expand Down Expand Up @@ -60,26 +61,35 @@ export function mergePageMetaDataArray(pageMetaData: PageMetaDatum[]) {
export function useCachedPages() {
const pages = new Map<string, string>()

function parseData(block?: SFCBlock) {
return {
content: block?.loc.source.trim() ?? '',
attr: block?.attrs ?? '',
async function parseData(filePath: string, sfcDescriptor: SFCDescriptor, options: ResolvedOptions) {
const routeSfcBlock = await getRouteSfcBlock(sfcDescriptor)
const routeBlock = await getRouteBlock(filePath, routeSfcBlock, options)
const pageMetaDatum: PageMetaDatum = {
path: normalizePath(filePath),
type: routeBlock?.attr.type ?? 'page',
}
const definePageData = await readPageOptionsFromMacro(sfcDescriptor)
Object.assign(pageMetaDatum, definePageData)
return pageMetaDatum
}

function setCache(filePath: string, routeBlock?: SFCBlock) {
pages.set(filePath, JSON.stringify(parseData(routeBlock)))
function setCache(filePath: string, pageMetaDatum?: PageMetaDatum) {
debug.cache('filePath', filePath)
const { path, ...rest } = pageMetaDatum ?? {}
pages.set(filePath, JSON.stringify(rest))
}

async function hasChanged(filePath: string, routeBlock?: SFCBlock) {
if (!routeBlock)
routeBlock = await getRouteSfcBlock(normalizePath(filePath))

return !pages.has(filePath) || JSON.stringify(parseData(routeBlock)) !== pages.get(filePath)
async function hasChanged(filePath: string, sfcDescriptor: SFCDescriptor, options: ResolvedOptions) {
const pageMetaDatum = await parseData(filePath, sfcDescriptor, options)
const { path, ...rest } = pageMetaDatum ?? {}
const changed = !pages.has(filePath) || JSON.stringify(rest) !== pages.get(filePath)
debug.cache('page changed', changed)
return changed
}

return {
setCache,
hasChanged,
parseData,
}
}
17 changes: 17 additions & 0 deletions packages/playground/src/pages/test-define-page.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script lang="ts" setup>
import {definePage} from "@uni-helper/vite-plugin-uni-pages/src";
definePage(()=>{
return {
style:{
backgroundColor: '#ffffff'
},
cus:88811155
}
})
const xm = ref("123")
</script>

<template>
<div>definePage2222</div>{{xm}}
</template>

11 changes: 8 additions & 3 deletions test/parser.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { resolve } from 'node:path'
import fs from 'node:fs'
import { describe, expect, it } from 'vitest'
import { getRouteBlock, getRouteSfcBlock, resolveOptions } from '../packages/core/src/index'
import { getRouteBlock, getRouteSfcBlock, parseSFC, resolveOptions } from '../packages/core/src/index'

const options = resolveOptions({})
const pagesJson = 'packages/playground/src/pages/test-json.vue'
Expand All @@ -9,7 +10,9 @@ const pagesYaml = 'packages/playground/src/pages/test-yaml.vue'
describe('parser', () => {
it('custom block', async () => {
const path = resolve(pagesJson)
const str = await getRouteSfcBlock(path)
const content = fs.readFileSync(path, 'utf8')
const sfcDescriptor = await parseSFC(content)
const str = await getRouteSfcBlock(sfcDescriptor)
const routeBlock = await getRouteBlock(path, str, options)
expect(routeBlock).toMatchInlineSnapshot(`
{
Expand All @@ -31,7 +34,9 @@ describe('parser', () => {

it('yaml comment', async () => {
const path = resolve(pagesYaml)
const str = await getRouteSfcBlock(path)
const content = fs.readFileSync(path, 'utf8')
const sfcDescriptor = await parseSFC(content)
const str = await getRouteSfcBlock(sfcDescriptor)
const routeBlock = await getRouteBlock(path, str, options)
expect(routeBlock).toMatchInlineSnapshot(`
{
Expand Down
Loading