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

fix: crash on windows #128

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion src/vite/plugins/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { PluginOption } from 'vite'

import type { ParsedConfig } from '../../config.js'
import { resolveVocsConfig } from '../utils/resolveVocsConfig.js'
import { getFsPath } from '../utils/paths.js'

const __dirname = dirname(fileURLToPath(import.meta.url))

Expand Down Expand Up @@ -50,7 +51,7 @@ export function dev(): PluginOption {
const indexHtml = readFileSync(resolve(__dirname, '../index.html'), 'utf-8')
const template = await server.transformIndexHtml(
url!,
indexHtml.replace(/\.\.\/app/g, `/@fs${resolve(__dirname, '../../app')}`),
indexHtml.replace(/\.\.\/app/g, getFsPath(resolve(__dirname, '../../app'))),
)
const module = await server.ssrLoadModule(
resolve(__dirname, '../../app/index.server.tsx'),
Expand Down
40 changes: 36 additions & 4 deletions src/vite/plugins/resolve-vocs-modules.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { dirname, extname, resolve } from 'node:path'
import { basename, dirname, extname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { type PluginOption } from 'vite'

import type { ParsedConfig } from '../../config.js'
import { resolveVocsConfig } from '../utils/resolveVocsConfig.js'
import { slash } from '../utils/slash.js'
import { isWin32 } from '../utils/paths.js'

const __dirname = dirname(fileURLToPath(import.meta.url))

// Get the result of `/node_modules/vocs/_lib`
const libDir = slash(resolve(__dirname, '../..').replace(process.cwd(), ''))

function getRelativeLibDir(path: string) {
// Default level: when the file is located in `docs/pages`
let level = 2

const name = basename(path)
const middle = path.replace('/pages/', '').replace(name, '')
const middles = middle.split('/').filter((i) => !!i)
if (middles.length > 0) level += middles.length

// e.g. `../../node_modules/vocs/_lib`
const levels = new Array(level).fill('..').join('/')
return `${levels}${libDir}`
}

export function resolveVocsModules(): PluginOption {
let config: ParsedConfig
return {
Expand All @@ -15,16 +34,29 @@ export function resolveVocsModules(): PluginOption {
config = (await resolveVocsConfig()).config
},
transform(code_, id) {
const resolvedRootDir = resolve(config.rootDir)
const resolvedId = resolve(id)

let code = code_
if (id.startsWith(resolve(config.rootDir))) {
if (resolvedId.startsWith(resolvedRootDir)) {
if (['.js', '.jsx', '.ts', '.tsx', '.md', '.mdx'].includes(extname(id))) {
// The relative path with file name
const relative = slash(resolvedId.replace(resolvedRootDir, ''))

code = code.replace(
/import (.*) from ("|')vocs("|')/g,
`import $1 from $2${resolve(__dirname, '../..')}$3`,
`import $1 from $2${
isWin32 ? getRelativeLibDir(relative) : resolve(__dirname, '../..')
}$3`,
)

code = code.replace(
/import (.*) from ("|')vocs\/components("|')/g,
`import $1 from $2${resolve(__dirname, '../../components')}$3`,
`import $1 from $2${
isWin32
? `${getRelativeLibDir(relative)}/components`
: resolve(__dirname, '../../components')
}$3`,
)
}
}
Expand Down
36 changes: 27 additions & 9 deletions src/vite/plugins/virtual-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { PluginOption } from 'vite'

import { resolveVocsConfig } from '../utils/resolveVocsConfig.js'
import { getGitTimestamp } from '../utils/getGitTimestamp.js'
import { padStartSlash, slash } from '../utils/slash.js'
import { isWin32 } from '../utils/paths.js'

export function virtualRoutes(): PluginOption {
const virtualModuleId = 'virtual:routes'
Expand Down Expand Up @@ -35,23 +37,34 @@ export function virtualRoutes(): PluginOption {
const pagesPath = resolve(rootDir, 'pages')

let code = 'export const routes = ['
for (const path of paths) {
const type = extname(path).match(/(mdx|md)/) ? 'mdx' : 'jsx'
const replacer = glob.split('*')[0]
for (const _path of paths) {
// _path is just a relative path starting with `pages`
const path = resolve(rootDir, _path)

// On Windows, unable to use full path to file for dynamic import
// will always prompt that the file cannot be found
const componentPath = isWin32 ? `./${rootDir}/${_path}` : path

const type = extname(path).match(/(mdx|md)/) ? 'mdx' : 'jsx'
const filePath = path.replace(`${pagesPath}/`, '')
const fileGitTimestamp = await getGitTimestamp(path)

// fileGitTimestamp can be `NaN` when not in git repo
let lastUpdatedAt: number | undefined
if (fileGitTimestamp) lastUpdatedAt = fileGitTimestamp

let pagePath = path.replace(replacer, '').replace(/\.(.*)/, '')
let pagePath = slash(path.replace(pagesPath, '').replace(/\.(.*)/, ''))

if (pagePath.endsWith('index'))
pagePath = pagePath.replace('index', '').replace(/\/$/, '')
code += ` { lazy: () => import("${path}"), path: "/${pagePath}", type: "${type}", filePath: "${filePath}", lastUpdatedAt: ${lastUpdatedAt} },`
code += ` { lazy: () => import("${componentPath}"), path: "${padStartSlash(
pagePath,
)}", type: "${type}", filePath: "${filePath}", lastUpdatedAt: ${lastUpdatedAt} },`

if (pagePath)
code += ` { lazy: () => import("${path}"), path: "/${pagePath}.html", type: "${type}", filePath: "${filePath}", lastUpdatedAt: ${lastUpdatedAt} },`
code += ` { lazy: () => import("${componentPath}"), path: "${padStartSlash(
pagePath,
)}.html", type: "${type}", filePath: "${filePath}", lastUpdatedAt: ${lastUpdatedAt} },`
}
code += ']'
return code
Expand All @@ -61,9 +74,14 @@ export function virtualRoutes(): PluginOption {
async buildStart() {
const { config } = await resolveVocsConfig()
const { rootDir } = config
const pagesPath = resolve(rootDir, 'pages')
glob = `${pagesPath}/**/*.{md,mdx,ts,tsx,js,jsx}`
paths = await globby(glob)

// Scan the routing files in the `pages` directory from rootDir,
// to obtain the scanning results of relative paths.
glob = 'pages/**/*.{md,mdx,ts,tsx,js,jsx}'
paths = await globby(glob, {
cwd: resolve(rootDir),
ignore: ['**/node_modules/**', '**/dist/**'],
})
},
handleHotUpdate() {
// TODO: handle changes
Expand Down
5 changes: 3 additions & 2 deletions src/vite/plugins/virtual-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type PluginOption } from 'vite'

import type { ParsedConfig, Theme } from '../../config.js'
import { resolveVocsConfig } from '../utils/resolveVocsConfig.js'
import { getFsPath } from '../utils/paths.js'

const __dirname = dirname(fileURLToPath(import.meta.url))

Expand Down Expand Up @@ -41,8 +42,8 @@ export function virtualStyles(): PluginOption {

const { config } = await resolveVocsConfig()
const { rootDir } = config
const themeStyles = resolve(__dirname, '../.vocs/theme.css')
const rootStyles = resolve(rootDir, 'styles.css')
const themeStyles = getFsPath(resolve(__dirname, '../.vocs/theme.css'))
const rootStyles = getFsPath(resolve(rootDir, 'styles.css'))
let code = ''
if (existsSync(themeStyles)) code += `import "${themeStyles}";`
if (existsSync(rootStyles)) code += `import "${rootStyles}";`
Expand Down
10 changes: 8 additions & 2 deletions src/vite/prerender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import pc from 'picocolors'
import type { Logger } from 'vite'
import { resolveOutDir } from './utils/resolveOutDir.js'
import { resolveVocsConfig } from './utils/resolveVocsConfig.js'
import { compatibleAbsolutePath } from './utils/paths.js'
import { slash } from './utils/slash.js'

type PrerenderParameters = { logger?: Logger; outDir?: string }

Expand All @@ -17,18 +19,22 @@ export async function prerender({ logger, outDir }: PrerenderParameters) {
const outDir_resolved = resolveOutDir(rootDir, outDir)

const template = readFileSync(resolve(outDir_resolved, 'index.html'), 'utf-8')
const mod = await import(resolve(__dirname, './.vocs/dist/index.server.js'))
const mod = await import(
compatibleAbsolutePath(resolve(__dirname, './.vocs/dist/index.server.js'))
)

// Get routes to prerender.
const routes = getRoutes(resolve(rootDir, 'pages'))
const routes = getRoutes(resolve(rootDir, 'pages')).map((i) => slash(i).replace('/index', '/'))

// Prerender each route.
for (const route of routes) {
const { head, body } = await mod.prerender(route)

let html = template
.replace('<!--body-->', body)
.replace('<!--head-->', head)
.replace('../app/utils/initializeTheme.ts', `${basePath}/initializeTheme.iife.js`)

if (theme?.colorScheme && theme?.colorScheme !== 'system')
html = html.replace('lang="en"', `lang="en" class="${theme.colorScheme}"`)
const isIndex = route.endsWith('/')
Expand Down
14 changes: 14 additions & 0 deletions src/vite/utils/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { platform } from 'node:os'
import { padStartSlash } from './slash.js'

export const isWin32 = platform() === 'win32'

export function getFsPath(path: string) {
return `/@fs${padStartSlash(path, false)}`
}

// Only file and data URLs are supported by the default ESM loader.
// On Windows, absolute paths must be valid `file://` URLs.
export function compatibleAbsolutePath(path: string) {
return isWin32 ? `file://${path}` : path
}
5 changes: 5 additions & 0 deletions src/vite/utils/slash.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export function slash(p: string): string {
return p.replace(/\\/g, '/')
}

export function padStartSlash(path: string, replaceSlash = true) {
const p = replaceSlash ? slash(path) : path
return p.startsWith('/') ? p : `/${p}`
}