Skip to content

Commit

Permalink
feat: suport definePage
Browse files Browse the repository at this point in the history
  • Loading branch information
CrazyZhang3 committed Aug 25, 2024
1 parent e7b3213 commit fa71a29
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 52 deletions.
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
87 changes: 87 additions & 0 deletions packages/core/src/definePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
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) {
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,
}
}

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>
}

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
>) {
}
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

0 comments on commit fa71a29

Please sign in to comment.