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

init

上级 23ec5d0a
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# vite-plugin-seo-prerender
`vite-plugin-seo-prerender` 插件是一个用于 `Vite` 构建工具的预渲染插件,它可以将你的单页面应用 (SPA) 在构建时静态预渲染为 HTML 文件,以提高首次加载速度和SEO友好性。适用于对站点少量页面生成静态HTML。支持 `Vue、React`等所有框架
***
+ 静态预渲染:将单页面应用在构建时预渲染为 HTML 文件。
+ SSG (静态站点生成):支持根据路由配置生成静态 HTML 文件。
+ 异步数据获取:支持在构建时获取异步数据并注入到预渲染的 HTML 文件中。
+ SEO 友好:生成的静态 HTML 文件对搜索引擎友好,可以更好地被爬虫索引。
## 安装使用
```shell
npm install vite-plugin-seo-prerender -D
# or
pnpm install vite-plugin-seo-prerender -D
# or
yarn install vite-plugin-seo-prerender -D
```
## 使用配置
```ts
// vite.config.ts
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
}
})
]
})
```
## 发布
运行 `vite build` 构建命令时即可生成HTML 文件
## 附seo关键词优化
```ts
// router.ts
const routes = [
{
path: '/about',
name: '/about',
component: () => import('./views/about/index.vue'),
meta: {
title: '关于我们',
keywords: '关键词1, 关键词2',
description: '关于我们描述'
}
}
]
router.afterEach((to, from, next) => {
const {title, keywords, description} = to.meta
if (title) {
document.title = title
}
if (keywords) {
const metaKeywords = document.querySelector('meta[name="keywords"]')
if (metaKeywords) {
metaKeywords.content = keywords
}
}
if (description) {
const metaDescription = document.querySelector('meta[name="description"]')
if (metaDescription) {
metaDescription.content = description
}
}
next()
})
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
<meta name="keywords" content=""/>
<meta name="description" content="">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
{
"name": "vite-plugin-seo-prerender",
"private": true,
"version": "0.2.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"publish": "pnpm --filter ./packages tsup"
},
"dependencies": {
"vue": "^3.3.4",
"vue-router": "^4.2.2"
},
"devDependencies": {
"@types/node": "^20.3.1",
"@vitejs/plugin-vue": "^4.2.3",
"puppeteer": "^20.7.3",
"tsup": "^7.0.0",
"typescript": "^5.1.3",
"vite": "^4.3.9",
"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`等所有框架
***
+ 静态预渲染:将单页面应用在构建时预渲染为 HTML 文件。
+ SSG (静态站点生成):支持根据路由配置生成静态 HTML 文件。
+ 异步数据获取:支持在构建时获取异步数据并注入到预渲染的 HTML 文件中。
+ SEO 友好:生成的静态 HTML 文件对搜索引擎友好,可以更好地被爬虫索引。
## 安装使用
```shell
npm install vite-plugin-seo-prerender -D
# or
pnpm install vite-plugin-seo-prerender -D
# or
yarn install vite-plugin-seo-prerender -D
```
## 使用配置
```ts
// vite.config.ts
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
}
})
]
})
```
## 发布
运行 `vite build` 构建命令时即可生成HTML 文件
## 附seo关键词优化
```ts
// router.ts
const routes = [
{
path: '/about',
name: '/about',
component: () => import('./views/about/index.vue'),
meta: {
title: '关于我们',
keywords: '关键词1, 关键词2',
description: '关于我们描述'
}
}
]
router.afterEach((to, from, next) => {
const {title, keywords, description} = to.meta
if (title) {
document.title = title
}
if (keywords) {
const metaKeywords = document.querySelector('meta[name="keywords"]')
if (metaKeywords) {
metaKeywords.content = keywords
}
}
if (description) {
const metaDescription = document.querySelector('meta[name="description"]')
if (metaDescription) {
metaDescription.content = description
}
}
next()
})
```
{
"name": "vite-plugin-seo-prerender",
"version": "0.0.1",
"description": "`vite-plugin-seo-prerender` 插件是一个用于 `Vite` 构建工具的预渲染插件,它可以将你的单页面应用 (SPA) 在构建时静态预渲染为 HTML 文件,以提高首次加载速度和SEO友好性。适用于对站点少量页面生成静态HTML。支持 `Vue、React`等所有框架",
"license": "MIT",
"author": "337547038",
"keywords": [
"prerender",
"seo",
"puppeteer",
"vue3",
"react",
"vite"
],
"repository": {
"type": "git",
"url": "https://github.com/337547038/vite-plugin-seo-prerender.git",
"directory": "packages"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"bugs": {
"url": "https://github.com/337547038/vite-plugin-seo-prerender/issues"
},
"homepage": "https://github.com/337547038/vite-plugin-seo-prerender",
"files": [
"dist"
],
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"scripts": {
"build": "vite build",
"tsup": "tsup"
},
"dependencies": {
"puppeteer": "^20.7.3"
},
"devDependencies": {
"@types/node": "^20.2.5",
"typescript": "^5.0.2"
}
}
import seoPrerender from './render'
import childProcess from 'child_process'
interface Config {
puppeteer?: any // puppeteer一些配置
routes: string[] // 需要生成的路由地址
removeStyle?: boolean // 启用vite preview会自带有些样式,默认下移除
callback?: Function
}
const prerender = (config: Config) => {
const cfgConfig = {
outDir: '',
mode: '',
root: '',
local: ''
}
return {
name: 'vitePluginSeoPrerender',
enforce: 'post',
configResolved(cfg: any) {
cfgConfig.outDir = cfg.build.outDir
cfgConfig.mode = cfg.mode
cfgConfig.root = cfg.root
},
buildEnd() {
//console.log('buildEnd')
},
closeBundle() {
if (!config?.routes?.length) {
console.log('路由地址为空,请配置需预渲染的routes')
return
}
// vite build 构建生产环境时才执行
if (cfgConfig.mode !== 'production') {
return
}
console.log('[vite-plugin-seo-prerender] is start..')
const cProcess = childProcess.exec('vite preview', (err, stdout, stderr) => {
if (err) {
console.error('执行命令时发生错误:', err);
return;
}
})
let localUrl
cProcess.stdout.on('data', async (data) => {
const local = data.match(/http:\/\/(.*?)\//g)
if (local && local.length && !localUrl) {
//转义并去掉最后一个/
localUrl = local[0].replace(/\x1B\[\d+m/g, '').slice(0, -1) // 控制台输出的有些会经过转义
console.log('Local: ' + localUrl)
cfgConfig.local = localUrl
await seoPrerender(Object.assign(config, cfgConfig))
// 在某个条件满足时,关闭进程退出
cProcess.kill('SIGTERM')
process.exit() // 关闭当前进程并退出
localUrl = ''
}
})
}
}
}
export default prerender
import puppeteer from 'puppeteer'
import fs from 'fs'
import path from 'path'
// 递归创建目录
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()
for (const item of config.routes) {
await page.goto(config.local + item)
await page.setViewport({width: 1024, height: 768})
let content = await page.content()
if (content.removeStyle !== false) {
// 若出现导常,可设置参数removeStyle:false
content = content.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "");
}
if (config.callback) {
content = config.callback(content, item)
}
if (item.indexOf('?') !== -1) {
// 填写的路由地址带有意外参数时不处理
console.log(`[vite-plugin-seo-prerender] ${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!`)
}
}
await browser.close();
console.log('[vite-plugin-seo-prerender] is complete')
}
export default seoPrerender
import { defineConfig } from 'tsup'
import pkg from './package.json'
import fs from 'fs'
fs.createReadStream('../README.md').pipe(fs.createWriteStream('./README.md'))
export default defineConfig(() => {
return {
entryPoints: ['src/index.ts'],
format: ['esm', 'cjs'],
skipNodeModulesBundle: true,
platform: 'node',
splitting: false,
minify: true,
sourcemap: false,
clean: true,
dts: false,
define: {
'process.env.NODE_ENV': '"production"',
__TEST__: 'false',
},
banner: {
js: `/**\n * name: ${pkg.name}\n * version: ${pkg.version}\n */`,
},
}
})
<template>
<router-view v-slot="{ Component }">
<component :is="Component"/>
</router-view>
</template>
<script setup>
</script>
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
import {createRouter, createWebHistory} from 'vue-router'
// @ts-ignore
const routes = [
{
path: '/',
name: '/test',
component: () => import('./views/test.vue')
},
{
path: '/about',
name: '/about',
component: () => import('./views/about/index.vue'),
meta: {
title: '关于我们',
keywords: '关键词3, 关键词4',
description: '关于我们描述'
}
}
]
// console.log(routes)
// 配置路由
const router = createRouter({
history: createWebHistory(),
//history: createWebHashHistory(),
routes: routes
})
router.afterEach((to, from, next) => {
const {title, keywords, description} = to.meta;
if (title) {
document.title = title;
}
if (keywords) {
const metaKeywords = document.querySelector('meta[name="keywords"]');
if (metaKeywords) {
metaKeywords.content = keywords;
}
}
if (description) {
const metaDescription = document.querySelector('meta[name="description"]');
if (metaDescription) {
metaDescription.content = description;
}
}
next()
})
export default router
<template>
<div>this about page</div>
</template>
<style scoped>
</style>
<template>
<div>
this is test page
</div>
</template>
<script setup>
</script>
/// <reference types="vite/client" />
declare module '*.vue' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
declare module '*.md' {
import type { ComponentOptions } from 'vue'
const Component: ComponentOptions
export default Component
}
{
"compilerOptions": {
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
//import seoPrerender from 'vite-plugin-seo-prerender'
import seoPrerender from './packages/src'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
seoPrerender({routes: ['/about']})
]
})
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册