From b78e063a2425385034e63fe48b190ac4c5ee9d7a Mon Sep 17 00:00:00 2001
From: 337547038 <337547038@qq.com>
Date: Fri, 30 Jun 2023 15:43:29 +0800
Subject: [PATCH] init
---
.gitignore | 24 ++++++++++
README.md | 85 ++++++++++++++++++++++++++++++++++
index.html | 15 ++++++
package.json | 25 ++++++++++
packages/README.md | 87 +++++++++++++++++++++++++++++++++++
packages/package.json | 53 +++++++++++++++++++++
packages/src/index.ts | 65 ++++++++++++++++++++++++++
packages/src/render.js | 46 ++++++++++++++++++
packages/tsup.config.ts | 25 ++++++++++
src/App.vue | 8 ++++
src/components/HelloWorld.vue | 38 +++++++++++++++
src/main.ts | 5 ++
src/router.ts | 48 +++++++++++++++++++
src/views/about/index.vue | 7 +++
src/views/test.vue | 8 ++++
src/vite-env.d.ts | 12 +++++
tsconfig.json | 24 ++++++++++
tsconfig.node.json | 10 ++++
vite.config.ts | 12 +++++
19 files changed, 597 insertions(+)
create mode 100644 .gitignore
create mode 100644 index.html
create mode 100644 package.json
create mode 100644 packages/README.md
create mode 100644 packages/package.json
create mode 100644 packages/src/index.ts
create mode 100644 packages/src/render.js
create mode 100644 packages/tsup.config.ts
create mode 100644 src/App.vue
create mode 100644 src/components/HelloWorld.vue
create mode 100644 src/main.ts
create mode 100644 src/router.ts
create mode 100644 src/views/about/index.vue
create mode 100644 src/views/test.vue
create mode 100644 src/vite-env.d.ts
create mode 100644 tsconfig.json
create mode 100644 tsconfig.node.json
create mode 100644 vite.config.ts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# 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?
diff --git a/README.md b/README.md
index d69397f..01d5c93 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,87 @@
# 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()
+})
+```
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..777ada7
--- /dev/null
+++ b/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ Vite + Vue + TS
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..85f4e0d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "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"
+ }
+}
diff --git a/packages/README.md b/packages/README.md
new file mode 100644
index 0000000..01d5c93
--- /dev/null
+++ b/packages/README.md
@@ -0,0 +1,87 @@
+# 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()
+})
+```
diff --git a/packages/package.json b/packages/package.json
new file mode 100644
index 0000000..95ed74a
--- /dev/null
+++ b/packages/package.json
@@ -0,0 +1,53 @@
+{
+ "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"
+ }
+}
diff --git a/packages/src/index.ts b/packages/src/index.ts
new file mode 100644
index 0000000..325ae1f
--- /dev/null
+++ b/packages/src/index.ts
@@ -0,0 +1,65 @@
+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
+
+
diff --git a/packages/src/render.js b/packages/src/render.js
new file mode 100644
index 0000000..b4f840e
--- /dev/null
+++ b/packages/src/render.js
@@ -0,0 +1,46 @@
+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(/
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..e455900
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,5 @@
+import {createApp} from 'vue'
+import App from './App.vue'
+import router from './router'
+
+createApp(App).use(router).mount('#app')
diff --git a/src/router.ts b/src/router.ts
new file mode 100644
index 0000000..7b56ddf
--- /dev/null
+++ b/src/router.ts
@@ -0,0 +1,48 @@
+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
diff --git a/src/views/about/index.vue b/src/views/about/index.vue
new file mode 100644
index 0000000..446cb11
--- /dev/null
+++ b/src/views/about/index.vue
@@ -0,0 +1,7 @@
+
+ this about page
+
+
+
diff --git a/src/views/test.vue b/src/views/test.vue
new file mode 100644
index 0000000..3d9a067
--- /dev/null
+++ b/src/views/test.vue
@@ -0,0 +1,8 @@
+
+
+ this is test page
+
+
+
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..1e213da
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,12 @@
+///
+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
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..b692355
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "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" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..2b2172a
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,12 @@
+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']})
+ ]
+})
--
GitLab