提交 f2c7ff13 编写于 作者: dsyuan001's avatar dsyuan001

Merge branch 'dev' of https://gitee.com/q337547038/vite-plugin-seo-prerender into main

# Conflicts:
#	pnpm-lock.yaml
#	vite.config.ts
# vite-plugin-seo-prerender
`vite-plugin-seo-prerender` 插件是一个用于 `Vite` 构建工具的预渲染插件,它可以将你的单页面应用 (SPA) 在构建时静态预渲染为 HTML 文件,以提高首次加载速度和SEO友好性。适用于对站点少量页面生成静态HTML。支持 `Vue、React`等所有框架
`vite-plugin-seo-prerender` 插件是一个用于 `Vite` 构建工具的预渲染插件,它可以将你的单页面应用 (SPA) 在构建时静态预渲染为
HTML 文件,以提高首次加载速度和SEO友好性。适用于对站点少量页面生成静态HTML。支持 `Vue、React`等所有框架
***
......@@ -8,7 +9,7 @@
+ SSG (静态站点生成):支持根据路由配置生成静态 HTML 文件。
+ 异步数据获取:支持在构建时获取异步数据并注入到预渲染的 HTML 文件中。
+ SEO 友好:生成的静态 HTML 文件对搜索引擎友好,可以更好地被爬虫索引。
+ 支持纯静态:public 目录下的 .html 支持动态引入样式及公共部分。
## 安装使用
......@@ -24,31 +25,54 @@ yarn install vite-plugin-seo-prerender -D
```ts
// vite.config.ts
import { defineConfig } from 'vite'
import {defineConfig} from 'vite'
import seoPrerender from 'vite-plugin-seo-prerender'
export default defineConfig({
plugins: [
seoPrerender({
puppeteer: {}, // puppeteer参数配置,可选
routes: [], // 需要生成的路由,必填
removeStyle:true, // 是否移除多余样式,默认true。在启动服务vite preview时会产生一些多余样式,如若丢失样式可设置为false
callback:(content,route)=>{
// 可对当前页面html内容进行一些替换等处理
// 一些处理逻辑...
return content
}
routes: [] // 需要生成的路由
})
]
})
```
## 纯静态开发
*使用预渲染生成的html页面有一个弊端,如预渲染生成页面 `/about/index.html`,它并不能通过 `http://xxx.com/about/index.html`
这样的形式正常访问,即使能正常展示也会丢失脚本事件*
对于部分特殊需求需要纯静态页面时,插件同样支持在编写 `public` 目录下的 `.html`
文件时,同样支持热更新及引入项目由 `scss、less`等编写的公共样式。并可使用指定标签替换页面内容,如公共头尾部等。
```html
<!--/public/about/index.html-->
<body>
<div><!--link href="/src/assets/header.html"--></div>
<div>这里的路径需要使用相对于根目录的绝对路径,不能使用相对路径,如 ./assets/header.html</div>
<div>this page content</div>
</body>
```
在浏览器输入如 `http://localhost/contact/index.html` 即可看到被替换后的页面,当修改`scss/less`文件或当前
html页时,可实现热更新。
## 发布
运行 `vite build` 构建命令时即可生成HTML 文件
运行 `vite build` 构建命令时即可生成 HTML 文件
## API
| 参数 | 类型 | 说明 |
|-------------|---------------------|-------------------------------------------------------------------------|
| puppeteer | object | puppeteer一些配置 |
| routes | string[] | 生成预渲染的路由path |
| removeStyle | boolean | 移除预览服务生成多余样式,默认true。如样式丢失,可设置为false |
| callback | funtion(html,route) | 预渲染和处理public下.html文件处理回调事件,可对需处理的页面进行修改,html为将要生成的文件内容,route当前处理的页面path |
| publicHtml | boolean/string[] | 需要处理的纯静态文件。true代表public整个目录下的html文件,数组时可指定文件,如['/contact/index.html'] |
| scss | [{entry,outDir}] | 需要编辑的单独scss文件。专为单独纯html定制,可将独立(即没有在项目里引入)的scss转换为css |
## 附:seo关键词优化设置
## 附:seo关键词优化路由设置
```ts
// router.ts
......
......@@ -10,6 +10,7 @@
"publish": "pnpm --filter ./packages tsup"
},
"dependencies": {
"vite-plugin-seo-prerender": "^0.1.0",
"vue": "^3.3.4",
"vue-router": "^4.2.2"
},
......@@ -18,11 +19,9 @@
"@vitejs/plugin-vue": "^4.2.3",
"puppeteer": "^20.7.4",
"sass": "^1.63.6",
"tsup": "^7.0.0",
"tsup": "^7.1.0",
"typescript": "^5.1.3",
"vite": "^4.3.9",
"vite-plugin-html": "^3.2.0",
"vite-plugin-legacy": "^2.1.0",
"vite-plugin-sprites": "^0.2.0",
"vue-tsc": "^1.8.0"
}
......
# vite-plugin-seo-prerender
`vite-plugin-seo-prerender` 插件是一个用于 `Vite` 构建工具的预渲染插件,它可以将你的单页面应用 (SPA) 在构建时静态预渲染为 HTML 文件,以提高首次加载速度和SEO友好性。适用于对站点少量页面生成静态HTML。支持 `Vue、React`等所有框架
`vite-plugin-seo-prerender` 插件是一个用于 `Vite` 构建工具的预渲染插件,它可以将你的单页面应用 (SPA) 在构建时静态预渲染为
HTML 文件,以提高首次加载速度和SEO友好性。适用于对站点少量页面生成静态HTML。支持 `Vue、React`等所有框架
***
......@@ -8,7 +9,7 @@
+ SSG (静态站点生成):支持根据路由配置生成静态 HTML 文件。
+ 异步数据获取:支持在构建时获取异步数据并注入到预渲染的 HTML 文件中。
+ SEO 友好:生成的静态 HTML 文件对搜索引擎友好,可以更好地被爬虫索引。
+ 支持纯静态:public 目录下的 .html 支持动态引入样式及公共部分。
## 安装使用
......@@ -24,31 +25,54 @@ yarn install vite-plugin-seo-prerender -D
```ts
// vite.config.ts
import { defineConfig } from 'vite'
import {defineConfig} from 'vite'
import seoPrerender from 'vite-plugin-seo-prerender'
export default defineConfig({
plugins: [
seoPrerender({
puppeteer: {}, // puppeteer参数配置,可选
routes: [], // 需要生成的路由,必填
removeStyle:true, // 是否移除多余样式,默认true。在启动服务vite preview时会产生一些多余样式,如若丢失样式可设置为false
callback:(content,route)=>{
// 可对当前页面html内容进行一些替换等处理
// 一些处理逻辑...
return content
}
routes: [] // 需要生成的路由
})
]
})
```
## 纯静态开发
*使用预渲染生成的html页面有一个弊端,如预渲染生成页面 `/about/index.html`,它并不能通过 `http://xxx.com/about/index.html`
这样的形式正常访问,即使能正常展示也会丢失脚本事件*
对于部分特殊需求需要纯静态页面时,插件同样支持在编写 `public` 目录下的 `.html`
文件时,同样支持热更新及引入项目由 `scss、less`等编写的公共样式。并可使用指定标签替换页面内容,如公共头尾部等。
```html
<!--/public/about/index.html-->
<body>
<div><!--link href="/src/assets/header.html"--></div>
<div>这里的路径需要使用相对于根目录的绝对路径,不能使用相对路径,如 ./assets/header.html</div>
<div>this page content</div>
</body>
```
在浏览器输入如 `http://localhost/contact/index.html` 即可看到被替换后的页面,当修改`scss/less`文件或当前
html页时,可实现热更新。
## 发布
运行 `vite build` 构建命令时即可生成HTML 文件
运行 `vite build` 构建命令时即可生成 HTML 文件
## API
| 参数 | 类型 | 说明 |
|-------------|---------------------|-------------------------------------------------------------------------|
| puppeteer | object | puppeteer一些配置 |
| routes | string[] | 生成预渲染的路由path |
| removeStyle | boolean | 移除预览服务生成多余样式,默认true。如样式丢失,可设置为false |
| callback | funtion(html,route) | 预渲染和处理public下.html文件处理回调事件,可对需处理的页面进行修改,html为将要生成的文件内容,route当前处理的页面path |
| publicHtml | boolean/string[] | 需要处理的纯静态文件。true代表public整个目录下的html文件,数组时可指定文件,如['/contact/index.html'] |
| scss | [{entry,outDir}] | 需要编辑的单独scss文件。专为单独纯html定制,可将独立(即没有在项目里引入)的scss转换为css |
## 附seo关键词优化
## 附:seo关键词优化路由设置
```ts
// router.ts
......
{
"name": "vite-plugin-seo-prerender",
"version": "0.0.1",
"version": "0.1.0",
"description": "`vite-plugin-seo-prerender` 插件是一个用于 `Vite` 构建工具的预渲染插件,它可以将你的单页面应用 (SPA) 在构建时静态预渲染为 HTML 文件,以提高首次加载速度和SEO友好性。适用于对站点少量页面生成静态HTML。支持 `Vue、React`等所有框架",
"license": "MIT",
"author": "337547038",
......@@ -46,7 +46,8 @@
"tsup": "tsup"
},
"dependencies": {
"puppeteer": "^20.7.3"
"puppeteer": "^20.7.3",
"sass": "^1.63.6"
},
"devDependencies": {
"@types/node": "^20.2.5",
......
import childProcess from 'child_process'
import path from 'path'
import seoPrerender from './render'
import publicHtml from "./public"
import {Config} from "./types"
import {createServer} from 'vite';
import fs from 'fs'
import puppeteer from 'puppeteer'
import * as sass from 'sass'
import prerender from './render'
// @ts-ignore
import publicHtml from './public'
import {getTransform, recursiveMkdir} from './utils'
let pPage
const prerender = (config: Config) => {
interface Scss {
entry: string
outDir: string
}
export interface Config {
puppeteer?: any // puppeteer一些配置
routes?: string[] // 需要生成的路由地址
removeStyle?: boolean // 启用vite preview会自带有些样式,默认下移除
callback?: Function
publicHtml?: boolean | string[] // public目录html文件处理
scss?: Scss[]
}
const getPublicHtml = (publicHtml) => {
let allUrl: string[] = []
if (typeof publicHtml === 'object') {
// 处理指定的
allUrl = publicHtml || []
}
const isAllUrl: boolean = typeof publicHtml === 'boolean' && publicHtml
return {allUrl, isAllUrl}
}
/**
* 将scss转换为css
* @param root
* @param css
*/
const transformSass = (root: string, css: Scss) => {
const entryDir: string = path.join(root, css.entry)
const result = sass.compile(entryDir)
const outDir: string = path.join(root, css.outDir)
recursiveMkdir(path.dirname(outDir))
fs.writeFileSync(outDir, result.css)
console.log(`transform scss: ${css.entry} => ${css.outDir}`)
}
const seoPrerender = (config: Config) => {
const cfgConfig = {
outDir: '',
mode: '',
......@@ -26,101 +63,78 @@ const prerender = (config: Config) => {
cfgConfig.root = cfg.root
cfgConfig.base = cfg.base
},
async buildStart() {
},
buildEnd() {
console.log('buildEnd,没看到有触发')
},
async load(id) {
},
transform(code, id) {
/*if (id.endsWith('.html')) {
console.log('transform:',id)
}*/
},
/*transformIndexHtml(html, tag) {
//console.log('transform',html)
},*/
transformIndexHtml: {
async transform(html, ctx) {
console.log('transform')
//console.log('html',html)
//console.log('ctx',ctx)
//ctx.moduleGraph.transformIndexHtml(html=>{})
}
},
async handleHotUpdate({file, server}) {
if (file.endsWith('.html')) {
/*console.log('file:',server)
// 启动一个浏览器服务
if (!pPage) {
const browser = await puppeteer.launch(Object.assign({headless: 'new'}, config.puppeteer || {}));
pPage = await browser.newPage()
await pPage.goto('http://127.0.0.1:5173')
await pPage.setViewport({width: 1024, height: 768})
}
pPage.content()
.then(html => {
console.log('page content', html)
})
.catch(res => {
console.log('catch', res)
})*/
buildStart() {
if (config?.scss?.length) {
config.scss.forEach((item: Scss) => {
transformSass(cfgConfig.root, item)
})
}
},
configureServer(server) {
if (config.html?.routes?.length) {
server.middlewares.use((req, res, next) => {
// console.log(server.moduleGraph)
const {allUrl, isAllUrl} = getPublicHtml(config?.publicHtml)
if (allUrl.length || isAllUrl) {
server.middlewares.use(async (req, res, next) => {
const baseUrl = req.url.replace(cfgConfig.base, '/')
console.log('base',baseUrl)
if (config.html.routes.includes(baseUrl)) {
console.log(req.url)
const module = server.moduleGraph.getModuleByUrl(req.url)
.then(res => {
console.log(res, 'okk')
})
const htmlContent = module ? module.content : '';
res.setHeader('Content-Type', 'text/html')
res.end('12');
return;
if ((isAllUrl && baseUrl.endsWith('.html')) || allUrl.includes(baseUrl)) {
const htmlContent: string = await publicHtml({
root: cfgConfig.root,
filePath: baseUrl,
mode: 'server',
callback: config.callback
})
if (htmlContent) {
res.setHeader('Content-Type', 'text/html')
res.end(htmlContent)
return
}
}
next()
})
}
// console.log('configureServer')
//const {watcher} = server
/*if (config.htmlRoutes?.length) {
watcher.on('change', async (filePath) => {
const relativePath = path.relative(server.config.root, filePath).replace('public', '').replace(/\\/g, '/')
if (config.htmlRoutes.includes(relativePath)) {
// 监听 public 目录下的指定 HTML 文件更改
let hostPort = '' // 获取启用的服务ip地址端口
const resolvedUrls = server.resolvedUrls
for (const key in resolvedUrls) {
if (resolvedUrls[key].length) {
hostPort = resolvedUrls[key][0]
}
}
await publicHtml(Object.assign(config,
{hostPort: hostPort, filePath: filePath}), 'dev')
},
handleHotUpdate({file, server}) {
// 更新时刷新当前页面
if (file.endsWith('.html')) {
const {allUrl, isAllUrl} = getPublicHtml(config?.publicHtml)
if (isAllUrl || allUrl.length) {
const publicPath = path.join(cfgConfig.root, 'public')
const dirPath = path.relative(publicPath, file)
server.ws.send({
type: 'full-reload',
path: '/' + getTransform(dirPath)
})
}
}
if (config?.scss?.length && file.endsWith('.scss')) {
const fileDir: string = getTransform(file)
config.scss.forEach((item: Scss) => {
if (fileDir.includes(item.entry)) {
transformSass(cfgConfig.root, item)
}
})
}*/
},
closeBundle() {
if (!config?.routes?.length) {
console.log('路由地址为空,请配置需预渲染的routes')
return
}
},
async closeBundle() {
// vite build 构建生产环境时才执行
if (cfgConfig.mode !== 'production') {
return
}
console.log('[vite-plugin-seo-prerender] is start..')
// 处理public下的html
const {allUrl, isAllUrl} = getPublicHtml(config?.publicHtml)
if (isAllUrl || allUrl.length) {
await publicHtml({
root: cfgConfig.root,
filePath: isAllUrl || allUrl,
mode: 'build',
outDir: cfgConfig.outDir,
callback: config.callback
})
}
if (!config?.routes?.length) {
//console.log('路由地址为空,请配置需预渲染的routes')
return
}
console.log('[vite-plugin-seo-prerender:routes] is start..')
const cProcess = childProcess.exec('vite preview', (err) => {
if (err) {
console.error('执行命令时发生错误:', err);
......@@ -135,7 +149,7 @@ const prerender = (config: Config) => {
localUrl = local[0].replace(/\x1B\[\d+m/g, '').slice(0, -1) // 控制台输出的有些会经过转义
console.log('Local: ' + localUrl)
cfgConfig.local = localUrl
await seoPrerender(Object.assign(config, cfgConfig))
await prerender(Object.assign(config, cfgConfig))
// 在某个条件满足时,关闭进程退出
cProcess.kill('SIGTERM')
process.exit() // 关闭当前进程并退出
......@@ -145,6 +159,7 @@ const prerender = (config: Config) => {
}
}
}
export default prerender
export default seoPrerender
......@@ -2,35 +2,149 @@
处理public静态文件,两个功能
1.将页面的公共样式及脚本动态插入到静态页,实现样式共用;
2.静态html也可以使用公共如头尾部*/
import puppeteer from 'puppeteer'
import {Config} from "./types"
import fs from 'fs'
import path from 'path'
import {getTransform} from './utils'
interface publicConfig extends Config {
hostPort: string
filePath: string
interface PublicConfig {
root: string
filePath: string | string[] | boolean
mode: string
outDir?: string
callback?: Function
}
let scriptLink
/**
* 获取主入口index的style和script
* 提取index.html中的入口script和link
* @param root 项目根目录 绝对位置路径
* @param mode 模式 server/build
* @param outDir 打包输入目录
*/
const getPublicIndex = async (config: publicConfig) => {
const browser = await puppeteer.launch(Object.assign({headless: 'new'}, config.puppeteer || {}));
const page = await browser.newPage()
await page.goto(config.hostPort)
await page.setViewport({width: 1024, height: 768})
const htmlContent = await page.content()
//提取link
const styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gi
const matches = htmlContent.matchAll(styleRegex)
console.log('style',matches)
for (const match of matches) {
const styleContent = match[1]
// 处理style标签中的内容
console.log(styleContent);
const getEntry = (root: string, mode: string, outDir?: string) => {
// 从入口html页面获取
const indexContent: string = fs.readFileSync(path.join(root, 'index.html'), 'utf-8')
if (mode === 'server') {
// 返回入口script即可 如/src/main.ts
const scriptMain = /<script[^>]*?\b\/main\b[^>]*?>.*?<\/script>/gi
const scripts = indexContent.match(scriptMain) || []
return scripts.join('\n')
} else {
// 提取动态插入index.html的link
const linkPattern = /<link[^>]*?rel=['|"]stylesheet['|"][^>]*?\.css[^>]*?>/gi
//const linkPattern = /(?<=<link.*?href=["'])(.*?\.css)(?=["'].*?)/g
const links = indexContent.match(linkPattern)
// 编译后的index.html
const newIndex: string = fs.readFileSync(path.join(root, outDir, 'index.html'), 'utf-8')
const newLinks = newIndex.match(linkPattern) || []
let resultLink: string[] = newLinks
if (links) {
resultLink = newLinks.filter((item: string) => !links.includes(item))
}
return resultLink.join('\n').replace('"./', '"/') // 将可能存在的href="./x"转为href="/"
}
}
/**
* 替换页面指定标签的内容
* @param html
* @param root
*/
const getReplaceComm = (html: string, root: string) => {
return html.replace(/<!--link\shref="(.*)"-->/gi, function (matches: string, m1: string) {
// m1就是匹配的路径地址了
// 读取m1文件内容
const dirPath = path.join(root, m1)
//console.log('dirPath',dirPath)
if (fs.existsSync(dirPath)) {
return fs.readFileSync(dirPath, {
encoding: 'utf8'
})
} else {
// 文件不存在时
return matches
}
})
}
const publicHtml = async (config: publicConfig, mode?: string) => {
const styleScript = await getPublicIndex(config)
/**
* 根据路径替换需要处理的html文件
* @param root 项目根目录 绝对路径
* @param dirPath 文件路径位置 绝对路径
* @param callback 回调处理事件
*/
const readWriteFile = (root: string, dirPath: string, callback: any) => {
const content: string = fs.readFileSync(dirPath, 'utf-8')
// 插入生成的css link
let replaceContent: string = content.replace('</head>', `${scriptLink}\n</head>`)
// 替换指定标签的内容
replaceContent = getReplaceComm(replaceContent, root)
if (typeof callback === 'function') {
replaceContent = callback(replaceContent, dirPath) || replaceContent
}
fs.writeFileSync(dirPath, replaceContent)
console.log('[vite-plugin-seo-prerender:publicHtml] ' + getTransform(path.relative(root, dirPath)))
}
/**
* 查找发布目录下所有.html文件
* @param dirPath
* @param indexPath 首页的路径 如dist/index.html
* @param root
* @param callback 回调处理事件
*/
const findHTMLFiles = (dirPath: string, indexPath: string, root: string, callback: any) => {
if (!fs.existsSync(dirPath)) {
console.log(`${dirPath}路径不存在`)
return
}
const paths: string[] = fs.readdirSync(dirPath)
paths.forEach((item: string) => {
// 如果是文件,则判断文件扩展名是否为html,排除根目录下的首页
const itemPath: string = path.join(dirPath, item)
if (fs.statSync(itemPath).isFile() && path.extname(itemPath) === '.html' && !getTransform(itemPath).endsWith(indexPath)) {
readWriteFile(root, itemPath, callback)
}
// 如果是目录,则递归地调用函数查找子目录中的HTML文件
if (fs.statSync(itemPath).isDirectory()) {
findHTMLFiles(itemPath, indexPath, root, callback)
}
})
}
/**
* 处理public目录下的html文件
* @param config
*/
const publicHtml = async (config: PublicConfig) => {
const {mode, root, filePath, outDir} = config
if (!scriptLink) { // 减少下每次读取index.html
scriptLink = getEntry(root, mode, outDir)
}
if (mode === 'server') {
const htmlFilePath: string = path.join(root, 'public', filePath as string)
let htmlContent: string = fs.readFileSync(htmlFilePath, 'utf-8')
//将script插入到body
htmlContent = htmlContent.replace('</body>', `${scriptLink}\n</body>`)
//替换公共部分
htmlContent = getReplaceComm(htmlContent, root)
if (typeof config.callback === 'function') {
htmlContent = config.callback(htmlContent, filePath) || htmlContent
}
return htmlContent
} else {
// 生产模式
if (typeof filePath === 'boolean') {
// 目录下的所有html文件
const dirPath: string = path.join(root, outDir)
const indexPath: string = getTransform(path.join(outDir, 'index.html'))
findHTMLFiles(dirPath, indexPath, root, config.callback)
} else {
// 指定目录
for (const key in filePath as string[]) {
const dirPath: string = path.join(root, outDir, filePath[key])
readWriteFile(root, dirPath, config.callback)
}
}
}
}
export default publicHtml
import puppeteer from 'puppeteer'
import fs from 'fs'
import path from 'path'
import {recursiveMkdir} from './utils'
// 递归创建目录
function recursiveMkdir(dirPath) {
const parentDir = path.dirname(dirPath); // 获取父级目录路径
if (!fs.existsSync(parentDir)) {
recursiveMkdir(parentDir); // 递归创建父级目录
}
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath); // 创建当前目录
}
}
const seoPrerender = async (config) => {
const browser = await puppeteer.launch(Object.assign({headless: 'new'}, config.puppeteer || {}));
const page = await browser.newPage()
const logTip = '[vite-plugin-seo-prerender:routes]'
for (const item of config.routes) {
await page.goto(config.local + item)
await page.goto(path.join(config.local, item))
await page.setViewport({width: 1024, height: 768})
let content = await page.content()
if (content.removeStyle !== false) {
let content: string = await page.content()
if (config.removeStyle !== false) {
// 若出现导常,可设置参数removeStyle:false
content = content.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "");
}
// 防止当设置了base:./形式时,会使用http的形式加载样式脚本资源,这里转为根路径
// 这里其实还存在问题,当直接访问xx/index.html 插入的公共资源也为./这样的形式,是加载不到的
const regLocal = new RegExp(config.local, 'g')
content = content.replace(regLocal, '')
if (config.callback) {
content = config.callback(content, item)
content = config.callback(content, item) || content
}
if (item.indexOf('?') !== -1) {
// 填写的路由地址带有意外参数时不处理
console.log(`[vite-plugin-seo-prerender] ${item} is error,unexpected?`)
console.log(`${logTip} ${item} is error,unexpected?`)
} else {
const fullPath = path.join(config.outDir, item)
recursiveMkdir(fullPath)
const filePath = path.join(fullPath, 'index.html')
fs.writeFileSync(filePath, content)
console.log(`[vite-plugin-seo-prerender] ${filePath} is success!`)
console.log(`${logTip} ${filePath.replace(/\\/g, '/')} is success!`)
}
}
await browser.close();
console.log('[vite-plugin-seo-prerender] is complete')
console.log(`${logTip} is complete`)
}
export default seoPrerender
export interface Config {
puppeteer?: any // puppeteer一些配置
routes?: string[] // 需要生成的路由地址
removeStyle?: boolean // 启用vite preview会自带有些样式,默认下移除
callback?: Function
html:{ // 处理public目录下的html文件
routes?: string[]
}
}
import fs from 'fs'
import path from 'path'
/**
* 将\转为/
* @param string
*/
export const getTransform = (string: string) => {
if (!string) {
return string
}
return string.replace(/\\/g, '/')
}
/**
* 递归创建目录
* @param dirPath
*/
export const recursiveMkdir = (dirPath:string) => {
const parentDir:string = path.dirname(dirPath) // 获取父级目录路径
if (!fs.existsSync(parentDir)) {
recursiveMkdir(parentDir) // 递归创建父级目录
}
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath) // 创建当前目录
}
}
此差异已折叠。
......@@ -3,14 +3,10 @@
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/style/test.css">
</head>
<body>
this is contact page ,transform 123?
<p>1.2.3.4.5.6.7.8.9.10.11.12.13.15.16.17.18
19.20.21.22.23.24.25.26.27.28.19.20.21.22.23.24.25.26.27.28
.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50
51.52.53.54.55.56
</p>
<!--link href="/src/assets/head.html"-->
<div>this is contact page</div>
</body>
</html>
body {
font-size: 14px;
}
\ No newline at end of file
<div>this is header</div>
<div>path: /src/assets/head.html</div>
body{margin: 0;padding: 0;font-size: 15px}
body{margin: 0;padding: 0;font-size: 16px}
body{font-size: 14px}
<template>
<div class="about">this about page</div>
<div class="about">this about page, count: {{count}}</div>
<div><button @click="count++">button</button></div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
const count =ref(0)
</script>
<style>
.about{color: red;font-size: 16px;}
</style>
......@@ -7,9 +7,13 @@ import seoPrerender from 'vite-plugin-seo-prerender'
export default defineConfig({
plugins: [
vue(),
// @ts-ignore
seoPrerender({
routes: ['/about'],
publishHtml: ['/contact/index.html']
publicHtml: true,
scss: [
{entry: '/src/assets/test.scss', outDir: '/public/style/test.css'}
]
})
]
})
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册