diff --git a/.gitignore b/.gitignore index 842110d23..a8c5765d0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,9 @@ *.log /_playwright-report /_playwright-results -/docs/preview.html /lib /node_modules # exceptions !.gitkeep +.vercel diff --git a/build/html.js b/build/html.js deleted file mode 100644 index 5e029efc8..000000000 --- a/build/html.js +++ /dev/null @@ -1,42 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import * as url from 'node:url'; -import prettier from 'prettier'; -import stripIndent from 'common-tags/lib/stripIndent/index.js'; - -const __filename = url.fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const prettierConfig = prettier.resolveConfig.sync(__dirname); - -// Preview -// ============================================================================= -function generatePreview() { - const comment = stripIndent` - - `; - const srcFile = 'index.html'; - const srcPath = path.resolve(__dirname, '..', 'docs'); - const srcHTML = fs.readFileSync(path.resolve(srcPath, srcFile), 'utf8'); - const outFile = 'preview.html'; - const outPath = path.resolve(__dirname, '..', 'docs'); - const outHTML = srcHTML - // Append comment - .replace(/()/, `${comment}\n$1`) - // Modify title - .replace(/(<\/title>)/, ' (Preview)$1') - // Replace CDN URLs with local paths - .replace(/\/\/cdn.jsdelivr.net\/npm\/docsify@4\//g, '/'); - const formattedHTML = prettier.format(outHTML, { - ...prettierConfig, - filepath: outFile, - }); - - console.log(`\nBuilding ${outFile} in ${outPath}`); - - fs.writeFileSync(path.resolve(outPath, outFile), formattedHTML); -} - -generatePreview(); diff --git a/jest.config.js b/jest.config.js index cc983caea..66ac68ded 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,6 @@ -import serverConfig from './server.config.js'; +import { testConfig } from './server.configs.js'; -const { hostname, port } = serverConfig.test; +const { hostname, port } = testConfig; const TEST_HOST = `http://${hostname}:${port}`; const sharedConfig = { errorOnDeprecated: true, diff --git a/middleware.js b/middleware.js new file mode 100644 index 000000000..2bc9f4e19 --- /dev/null +++ b/middleware.js @@ -0,0 +1,53 @@ +// Exports +// ============================================================================= +export const config = { + matcher: ['/preview/(index.html)?'], +}; + +// Rewrite rules shared with local server configurations +export const rewriteRules = [ + // Replace CDN URLs with local paths + { + match: /https?.*\/CHANGELOG.md/g, + replace: '/CHANGELOG.md', + }, + { + // CDN versioned default + // Ex1: //cdn.com/package-name + // Ex2: http://cdn.com/package-name@1.0.0 + // Ex3: https://cdn.com/package-name@latest + match: /(?:https?:)*\/\/.*cdn.*docsify[@\d.latest]*(?=["'])/g, + replace: '/lib/docsify.min.js', + }, + { + // CDN paths to local paths + // Ex1: //cdn.com/package-name/path/file.js => /path/file.js + // Ex2: http://cdn.com/package-name@1.0.0/dist/file.js => /dist/file.js + // Ex3: https://cdn.com/package-name@latest/dist/file.js => /dist/file.js + match: /(?:https?:)*\/\/.*cdn.*docsify[@\d.latest]*\/(?:lib\/)/g, + replace: '/lib/', + }, +]; + +// Serve virtual /preview/index.html +// Note: See vercel.json for preview routing configuration +// 1. Fetch index.html from /docs/ directory +// 2. Replace CDN URLs with local paths (see rewriteRules) +// 3. Return preview HTML +export default async function middleware(request) { + const { origin } = new URL(request.url); + const indexURL = `${origin}/docs/index.html`; + const indexHTML = await fetch(indexURL).then(res => res.text()); + const previewHTML = rewriteRules.reduce( + (html, rule) => html.replace(rule.match, rule.replace), + indexHTML + ); + + return new Response(previewHTML, { + status: 200, + headers: { + 'content-type': 'text/html', + 'x-robots-tag': 'noindex', + }, + }); +} diff --git a/package.json b/package.json index f8f81eeca..1701ca9fc 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,9 @@ "build:css:min": "node build/mincss.js", "build:css": "mkdirp lib/themes && node build/css -o lib/themes", "build:emoji": "node ./build/emoji.js", - "build:html": "node ./build/html.js", "build:js": "cross-env NODE_ENV=production node build/build.js", "build:test": "npm run build && npm test", - "build": "rimraf lib themes && run-s build:js build:css build:css:min build:cover build:emoji build:html", + "build": "rimraf lib themes && run-s build:js build:css build:css:min build:cover build:emoji", "dev": "run-p serve:dev watch:*", "docker:build:test": "npm run docker:cli -- build:test", "docker:build": "docker build -f Dockerfile -t docsify-test:local .", @@ -49,13 +48,13 @@ "prettier": "prettier . --write", "pub:next": "cross-env RELEASE_TAG=next sh build/release.sh", "pub": "sh build/release.sh", - "serve:dev": "npm run build:html && npm run serve -- --dev", + "serve:dev": "npm run serve -- --dev", "serve": "node server", "test:e2e": "playwright test", "test:integration": "npm run jest -- --selectProjects integration", "test:unit": "npm run jest -- --selectProjects unit", "test": "npm run jest && run-s test:e2e", - "watch:css": "npm run css -- -o themes -w", + "watch:css": "npm run build:css -- -w", "watch:js": "node build/build.js" }, "husky": { diff --git a/playwright.config.js b/playwright.config.js index 49f3bf874..2c7e8894d 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,7 +1,7 @@ import { devices } from '@playwright/test'; -import serverConfig from './server.config.js'; +import { testConfig } from './server.configs.js'; -const { hostname, port } = serverConfig.test; +const { hostname, port } = testConfig; const TEST_HOST = `http://${hostname}:${port}`; process.env.TEST_HOST = TEST_HOST; diff --git a/server.config.js b/server.configs.js similarity index 52% rename from server.config.js rename to server.configs.js index d33765749..2f0889c72 100644 --- a/server.config.js +++ b/server.configs.js @@ -1,54 +1,46 @@ import * as path from 'node:path'; import * as url from 'node:url'; +import { rewriteRules } from './middleware.js'; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const defaults = { + +// Production (CDN URLs, watch disabled) +export const prodConfig = { hostname: '127.0.0.1', notify: false, open: false, - rewriteRules: [ - // Replace remote URLs with local paths - { - // Changelog - match: /https?.*\/CHANGELOG.md/g, - replace: '/CHANGELOG.md', - }, - ], + port: 8080, + server: { + baseDir: './docs', + }, + snippet: false, + ui: false, +}; + +// Development (local URLs, watch enabled) +export const devConfig = { + ...prodConfig, + files: ['CHANGELOG.md', 'docs/**/*', 'lib/**/*'], + port: 3000, + rewriteRules, server: { - baseDir: 'docs', - index: 'preview.html', + ...prodConfig.server, routes: { '/changelog.md': path.resolve(__dirname, 'CHANGELOG.md'), '/lib': path.resolve(__dirname, 'lib'), '/node_modules': path.resolve(__dirname, 'node_modules'), // Required for automated Vue tests }, }, - snippet: false, - ui: false, + snippet: true, }; -export default { - // Development (preview, local URLs, watch enabled) - dev: { - ...defaults, - files: ['CHANGELOG.md', 'docs/**/*', 'lib/**/*'], - port: 3000, - open: true, - snippet: true, - }, - // Production (index, CDN URLs, watch disabled) - prod: { - ...defaults, - port: 8080, - server: { - ...defaults.server, - index: 'index.html', - }, - }, - // Test (preview, local URLs, watch disabled) - test: { - ...defaults, +// Test (local URLs, watch disabled) +export const testConfig = { + ...devConfig, + port: 4000, + server: { + ...devConfig.server, middleware: [ // Blank page required for test environment { @@ -60,6 +52,7 @@ export default { }, }, ], - port: 4000, }, + snippet: false, + watch: false, }; diff --git a/server.js b/server.js index 1d1c834d2..4d58c713b 100644 --- a/server.js +++ b/server.js @@ -1,13 +1,14 @@ import { create } from 'browser-sync'; -import serverConfigs from './server.config.js'; +import { devConfig, prodConfig } from './server.configs.js'; const bsServer = create(); const args = process.argv.slice(2); -const configName = - Object.keys(serverConfigs).find(name => args.includes(`--${name}`)) || 'prod'; -const settings = serverConfigs[configName]; +const config = args.includes('--dev') ? devConfig : prodConfig; +const configName = config === devConfig ? 'development' : 'production'; +const isWatch = Boolean(config.files) && config.watch !== false; +const urlType = config === devConfig ? 'local' : 'CDN'; // prettier-ignore -console.log(`\nStarting ${configName} server (${settings.server.index}, watch: ${Boolean(settings.files)})\n`); +console.log(`\nStarting ${configName} server (${urlType} URLs, watch: ${isWatch})\n`); -bsServer.init(settings); +bsServer.init(config); diff --git a/test/config/server.js b/test/config/server.js index f5c8fa0c6..d896b58f6 100644 --- a/test/config/server.js +++ b/test/config/server.js @@ -1,13 +1,13 @@ import * as process from 'node:process'; import { create } from 'browser-sync'; -import config from '../../server.config.js'; +import { testConfig } from '../../server.configs.js'; const bsServer = create(); export async function startServer() { // Wait for server to start return new Promise(resolve => { - const settings = config.test; + const settings = testConfig; console.log('\n'); diff --git a/vercel.json b/vercel.json index 79ff17433..da3464f69 100644 --- a/vercel.json +++ b/vercel.json @@ -1,9 +1,13 @@ { - "redirects": [ + "headers": [ { - "source": "/", - "destination": "./docs/preview.html", - "permanent": true + "source": "/(.*)", + "headers": [{ "key": "x-robots-tag", "value": "noindex" }] } + ], + "redirects": [{ "source": "/", "destination": "/preview/" }], + "rewrites": [ + { "source": "/preview/CHANGELOG.md", "destination": "/CHANGELOG.md" }, + { "source": "/preview/:path*", "destination": "/docs/:path*" } ] }