提交 b7f14f1e 编写于 作者: fxy060608's avatar fxy060608

feat(cli): ssr

上级 78f41bbb
import fs from 'fs'
import os from 'os'
import path from 'path'
import slash from 'slash'
import { once } from '@dcloudio/uni-shared'
const isWindows = os.platform() === 'win32'
export function normalizePath(id: string): string {
return path.posix.normalize(isWindows ? slash(id) : id)
}
export const resolveMainPathOnce = once((inputDir: string) => {
const mainTsPath = path.resolve(inputDir, 'main.ts')
if (fs.existsSync(mainTsPath)) {
return mainTsPath
}
return path.resolve(inputDir, 'main.js')
})
......@@ -61,14 +61,18 @@ export default /*#__PURE__*/ defineComponent({
.split('\n')
const len = lines.length - 1
lines.forEach((text, index) => {
children.push(
createTextVNode(
normalizeText(text, {
space: props.space as DecodeOptions['space'],
decode: props.decode as boolean,
})
if (index === 0 && !text) {
//临时方案解决(<text>\n横向布局</text>) Hydration node mismatch
} else {
children.push(
createTextVNode(
normalizeText(text, {
space: props.space as DecodeOptions['space'],
decode: props.decode as boolean,
})
)
)
)
}
if (index !== len) {
children.push(createVNode('br'))
}
......@@ -89,7 +93,7 @@ export default /*#__PURE__*/ defineComponent({
}
return (
<uni-text selectable={props.selectable}>
{<span>{children}</span>}
{createVNode('span', null, children)}
</uni-text>
)
}
......
......@@ -153,35 +153,6 @@ function updateCssVar(cssVars) {
function updatePageCssVar(cssVars) {
return updateCssVar(cssVars);
}
const sheetsMap = new Map();
function updateStyle(id, content) {
let style = sheetsMap.get(id);
if (style && !(style instanceof HTMLStyleElement)) {
removeStyle(id);
style = void 0;
}
if (!style) {
style = document.createElement("style");
style.setAttribute("type", "text/css");
style.innerHTML = content;
document.head.appendChild(style);
} else {
style.innerHTML = content;
}
sheetsMap.set(id, style);
}
function removeStyle(id) {
let style = sheetsMap.get(id);
if (style) {
if (style instanceof CSSStyleSheet) {
document.adoptedStyleSheets.indexOf(style);
document.adoptedStyleSheets = document.adoptedStyleSheets.filter((s) => s !== style);
} else {
document.head.removeChild(style);
}
sheetsMap.delete(id);
}
}
function PolySymbol(name) {
return Symbol(process.env.NODE_ENV !== "production" ? "[uni-app]: " + name : name);
}
......@@ -6451,10 +6422,14 @@ var index$8 = /* @__PURE__ */ vue.defineComponent({
const lines = vnode.children.replace(/\\n/g, "\n").split("\n");
const len = lines.length - 1;
lines.forEach((text, index2) => {
children.push(vue.createTextVNode(normalizeText(text, {
space: props2.space,
decode: props2.decode
})));
if (index2 === 0 && !text)
;
else {
children.push(vue.createTextVNode(normalizeText(text, {
space: props2.space,
decode: props2.decode
})));
}
if (index2 !== len) {
children.push(vue.createVNode("br"));
}
......@@ -6469,7 +6444,7 @@ var index$8 = /* @__PURE__ */ vue.defineComponent({
}
return vue.createVNode("uni-text", {
selectable: props2.selectable
}, [vue.createVNode("span", null, [children])], 8, ["selectable"]);
}, [vue.createVNode("span", null, children)], 8, ["selectable"]);
};
}
});
......@@ -9008,7 +8983,6 @@ function usePageHeadButtons({
if (!fontFamily) {
fontFamily = `font${Date.now()}`;
fonts[fontSrc] = fontFamily;
updateStyle("uni-btn-" + fontFamily, `@font-face{font-family: "${fontFamily}";src: url("${fontSrc}") format("truetype")}`);
}
btn.fontFamily = fontFamily;
}
......
......@@ -10061,10 +10061,14 @@ var index$8 = /* @__PURE__ */ defineComponent({
const lines = vnode.children.replace(/\\n/g, "\n").split("\n");
const len = lines.length - 1;
lines.forEach((text2, index2) => {
children.push(createTextVNode(normalizeText(text2, {
space: props2.space,
decode: props2.decode
})));
if (index2 === 0 && !text2)
;
else {
children.push(createTextVNode(normalizeText(text2, {
space: props2.space,
decode: props2.decode
})));
}
if (index2 !== len) {
children.push(createVNode("br"));
}
......@@ -10079,7 +10083,7 @@ var index$8 = /* @__PURE__ */ defineComponent({
}
return createVNode("uni-text", {
selectable: props2.selectable
}, [createVNode("span", null, [children])], 8, ["selectable"]);
}, [createVNode("span", null, children)], 8, ["selectable"]);
};
}
});
......@@ -15278,7 +15282,7 @@ function usePageHeadButtons({
if (!fontFamily) {
fontFamily = `font${Date.now()}`;
fonts[fontSrc] = fontFamily;
updateStyle("uni-btn-" + fontFamily, `@font-face{font-family: "${fontFamily}";src: url("${fontSrc}") format("truetype")}`);
onBeforeMount(() => updateStyle("uni-btn-" + fontFamily, `@font-face{font-family: "${fontFamily}";src: url("${fontSrc}") format("truetype")}`));
}
btn.fontFamily = fontFamily;
}
......
import { computed, defineComponent, ref } from 'vue'
import { computed, defineComponent, onBeforeMount, ref } from 'vue'
import { extend, isArray } from '@vue/shared'
import { Input } from '@dcloudio/uni-components'
import { getRealPath } from '@dcloudio/uni-platform'
......@@ -309,9 +309,11 @@ function usePageHeadButtons({ id, navigationBar }: UniApp.PageRouteMeta) {
if (!fontFamily) {
fontFamily = `font${Date.now()}`
fonts[fontSrc] = fontFamily
updateStyle(
'uni-btn-' + fontFamily,
`@font-face{font-family: "${fontFamily}";src: url("${fontSrc}") format("truetype")}`
onBeforeMount(() =>
updateStyle(
'uni-btn-' + fontFamily,
`@font-face{font-family: "${fontFamily}";src: url("${fontSrc}") format("truetype")}`
)
)
}
btn.fontFamily = fontFamily
......
......@@ -8,12 +8,12 @@ function createApp(App) {
return AppInstance
}
export async function render(url, manifest) {
export async function render(url, manifest = {}) {
const app = AppInstance
const router = app.router
// set the router to the desired URL before rendering
router.push(url)
await router.push(url)
await router.isReady()
// passing SSR context object which will be available via useSSRContext()
......
......@@ -30,6 +30,7 @@
"chalk": "^4.1.1",
"debug": "^4.3.1",
"estree-walker": "^2.0.1",
"express": "^4.17.1",
"fs-extra": "^9.0.1",
"jsonc-parser": "^3.0.0",
"magic-string": "^0.25.7",
......@@ -46,6 +47,7 @@
"vite": "^2.2.3"
},
"devDependencies": {
"@types/express": "^4.17.11",
"@types/mime": "^2.0.3",
"@types/module-alias": "^2.0.0",
"@types/sass": "^1.16.0",
......
import { build as buildByVite, BuildOptions } from 'vite'
import { CliOptions } from '.'
import { cleanOptions } from './utils'
export async function build(options: CliOptions) {
await buildByVite({
root: process.env.VITE_ROOT_DIR,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
build: cleanOptions(options) as BuildOptions,
})
}
export async function buildSSR(options: CliOptions) {}
import { cac } from 'cac'
import chalk from 'chalk'
import {
LogLevel,
ServerOptions,
build,
createServer,
createLogger,
BuildOptions,
} from 'vite'
import { LogLevel, createLogger, ServerOptions } from 'vite'
import { build, buildSSR } from './build'
import { createServer, createSSRServer } from './server'
import { initEnv, PLATFORMS } from './utils'
......@@ -29,26 +24,8 @@ export interface CliOptions {
clearScreen?: boolean
}
function cleanOptions(options: CliOptions) {
const ret = { ...options }
delete ret['--']
delete ret.platform
delete ret.p
delete ret.ssr
delete ret.debug
delete ret.d
delete ret.filter
delete ret.f
delete ret.logLevel
delete ret.l
delete ret.clearScreen
return ret
}
cli
.option('-p,--platform [platform]', '[string] ' + PLATFORMS.join(' | '), {
.option('-p, --platform [platform]', '[string] ' + PLATFORMS.join(' | '), {
default: 'h5',
})
.option('-ssr', '[boolean] server-side rendering', {
......@@ -72,16 +49,10 @@ cli
'--force',
`[boolean] force the optimizer to ignore the cache and re-bundle`
)
.action(async (options: CliOptions) => {
.action(async (options: CliOptions & ServerOptions) => {
initEnv(options)
try {
const server = await createServer({
root: process.env.VITE_ROOT_DIR,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
server: cleanOptions(options) as ServerOptions,
})
await server.listen()
await (options.ssr ? createSSRServer(options) : createServer(options))
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error when starting dev server:\n${e.stack}`)
......@@ -117,12 +88,7 @@ cli
.action(async (options: CliOptions) => {
initEnv(options)
try {
await build({
root: process.env.VITE_ROOT_DIR,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
build: cleanOptions(options) as BuildOptions,
})
await (options.ssr ? buildSSR(options) : build(options))
} catch (e) {
createLogger(options.logLevel).error(
chalk.red(`error during build:\n${e.stack}`)
......
import os from 'os'
import fs from 'fs'
import path from 'path'
import chalk from 'chalk'
import {
createLogger,
createServer as createViteServer,
ServerOptions,
} from 'vite'
import express from 'express'
import { hasOwn } from '@vue/shared'
import { resolveMainPathOnce } from '@dcloudio/uni-cli-shared'
import { CliOptions } from '.'
import { cleanOptions } from './utils'
export async function createServer(options: CliOptions & ServerOptions) {
const server = await createViteServer({
root: process.env.VITE_ROOT_DIR,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
server: cleanOptions(options) as ServerOptions,
})
await server.listen()
}
export async function createSSRServer(options: CliOptions & ServerOptions) {
const app = express()
/**
* @type {import('vite').ViteDevServer}
*/
const vite = await createViteServer({
root: process.env.VITE_ROOT_DIR,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
server: {
middlewareMode: true,
watch: {
// During tests we edit the files too fast and sometimes chokidar
// misses change events, so enforce polling for consistency
usePolling: true,
interval: 100,
},
},
})
// use vite's connect instance as middleware
app.use(vite.middlewares)
app.use('*', async (req, res) => {
try {
const url = req.originalUrl
const template = await vite.transformIndexHtml(
url,
fs.readFileSync(
path.resolve(process.env.VITE_ROOT_DIR!, 'index.html'),
'utf-8'
)
)
const render = (
await vite.ssrLoadModule(resolveMainPathOnce(process.env.UNI_INPUT_DIR))
).render
const [appHtml, preloadLinks] = await render(url)
const html = template
.replace(`<!--preload-links-->`, preloadLinks)
.replace(`<!--app-html-->`, appHtml)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite && vite.ssrFixStacktrace(e)
res.status(500).end(e.stack)
}
})
const logger = createLogger(options.logLevel)
const serverOptions = vite.config.server || {}
const protocol = (
hasOwn(options, 'https') ? options.https : serverOptions.https
)
? 'https'
: 'http'
let port = options.port || serverOptions.port || 3000
const hostname = options.host
return new Promise((resolve, reject) => {
const onSuccess = () => {
const interfaces = os.networkInterfaces()
Object.keys(interfaces).forEach((key) =>
(interfaces[key] || [])
.filter((details) => details.family === 'IPv4')
.map((detail) => {
return {
type: detail.address.includes('127.0.0.1')
? 'Local: '
: 'Network: ',
host: detail.address,
}
})
.forEach(({ type, host }) => {
const url = `${protocol}://${host}:${chalk.bold(port)}${
vite.config.base
}`
logger.info(` > ${type} ${chalk.cyan(url)}`)
})
)
resolve(server)
}
const onError = (e: Error & { code?: string }) => {
if (e.code === 'EADDRINUSE') {
if (options.strictPort) {
server.off('error', onError)
reject(new Error(`Port ${port} is already in use`))
} else {
logger.info(`Port ${port} is in use, trying another one...`)
app.listen(++port, hostname!, onSuccess)
}
} else {
server.off('error', onError)
reject(e)
}
}
const server = app.listen(port, hostname!, onSuccess).on('error', onError)
})
}
......@@ -20,3 +20,21 @@ export function initEnv(options: CliOptions) {
process.env.UNI_PLATFORM = options.platform as UniApp.PLATFORM
}
export function cleanOptions(options: CliOptions) {
const ret = { ...options }
delete ret['--']
delete ret.platform
delete ret.p
delete ret.ssr
delete ret.debug
delete ret.d
delete ret.filter
delete ret.f
delete ret.logLevel
delete ret.l
delete ret.clearScreen
return ret
}
......@@ -50,6 +50,9 @@ export function uniManifestJsonPlugin(
const { debug, h5 } = manifest
const appid = (manifest.appid || '').replace('__UNI__', '')
const router = { ...defaultRouter, ...((h5 && h5.router) || {}) }
if (!router.base) {
router.base = '/'
}
const async = define.__UNI_FEATURE_PAGES__
? { ...defaultAsync, ...((h5 && h5.async) || {}) }
: {}
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册