omio - add rendeToString

This commit is contained in:
张磊 2019-01-16 10:08:43 +08:00
parent d0afe99bdd
commit 21e8bbcb3a
10 changed files with 2477 additions and 4 deletions

View File

@ -616,7 +616,7 @@ render(<todo-app />, 'body')
### Store
Omi Store Architecture: Injected from the root component and shared across all subcomponents. It's very simple to use:
Omi Store provides a way to pass data through the component tree without having to pass props down manually at every level, injected from the root component and shared across all subcomponents. It's very simple to use:
```js
import { define, render, WeElement } from 'omi'

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,9 @@
<html>
<head></head>
<body>
<script src="b.js"></script>
</body>
</html>

View File

@ -0,0 +1,57 @@
import { render, WeElement, define, renderToString} from '../../src/omi'
define('todo-list', class extends WeElement {
render(props) {
return (
<ul>
{props.items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
)
}
})
define('todo-app', class extends WeElement {
static observe = true
data = { items: [], text: '' }
render() {
return (
<div>
<h3>TODO</h3>
<todo-list items={this.data.items} />
<form onSubmit={this.handleSubmit}>
<input
id="new-todo"
onChange={this.handleChange}
value={this.data.text}
/>
<button>Add #{this.data.items.length + 1}</button>
</form>
</div>
)
}
handleChange = e => {
this.data.text = e.target.value
}
handleSubmit = e => {
e.preventDefault()
if (!this.data.text.trim().length) {
return
}
this.data.items.push({
text: this.data.text,
id: Date.now()
})
this.data.text = ''
}
})
render(<todo-app />, 'body')
console.log(renderToString(<todo-app />))

View File

@ -23,6 +23,7 @@
"fire": "rollup -c config/rollup.example.js --watch",
"receive-props": "rollup -c config/rollup.example.js --watch",
"rpx": "rollup -c config/rollup.example.js --watch",
"render-to-string": "rollup -c config/rollup.example.js --watch",
"mvvm": "rollup -c config/rollup.example.js --watch",
"omi-tap": "rollup -c config/rollup.example.js --watch",
"simple": "rollup -c config/rollup.example.js --watch",

View File

@ -9,6 +9,7 @@ import { rpx } from './rpx'
import ModelView from './model-view'
import { classNames, extractClass } from './class'
import { getHost } from './get-host'
import { renderToString } from './render-to-string'
const WeElement = Component
const defineElement = define
@ -32,7 +33,8 @@ options.root.Omi = {
defineElement,
classNames,
extractClass,
getHost
getHost,
renderToString
}
options.root.omi = Omi
options.root.Omi.version = 'omio-1.3.2'
@ -53,7 +55,8 @@ export default {
defineElement,
classNames,
extractClass,
getHost
getHost,
renderToString
}
export {
@ -72,5 +75,6 @@ export {
defineElement,
classNames,
extractClass,
getHost
getHost,
renderToString
}

View File

@ -0,0 +1,190 @@
/**
* preact-render-to-string based on preact-render-to-string
* by Jason Miller
* Licensed under the MIT License
* https://github.com/developit/preact-render-to-string
*
* modified by dntzhang
*/
import options from './options'
const encodeEntities = s => String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
const mapping = options.mapping
const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
/** The default export is an alias of `render()`. */
export function renderToString(vnode, store, opts, isSvgMode) {
if (vnode == null || typeof vnode === 'boolean') {
return '';
}
let nodeName = vnode.nodeName,
attributes = vnode.attributes,
isComponent = false;
store = store || {};
opts = opts || {};
let pretty = true && opts.pretty,
indentChar = pretty && typeof pretty === 'string' ? pretty : '\t';
// #text nodes
if (typeof vnode !== 'object' && !nodeName) {
return encodeEntities(vnode);
}
// components
const ctor = mapping[nodeName]
if (ctor) {
isComponent = true;
let props = getNodeProps(vnode),
rendered;
// class-based components
let c = new ctor(props, store);
// turn off stateful re-rendering:
c._disable = c.__x = true;
c.props = props;
c.store = store;
if (c.install) c.install();
if (c.beforeRender) c.beforeRender();
rendered = c.render(c.props, c.data, c.store);
return renderToString(rendered, store, opts);
}
// render JSX to HTML
let s = '', html;
if (attributes) {
let attrs = Object.keys(attributes);
// allow sorting lexicographically for more determinism (useful for tests, such as via preact-jsx-chai)
if (opts && opts.sortAttributes === true) attrs.sort();
for (let i = 0; i < attrs.length; i++) {
let name = attrs[i],
v = attributes[name];
if (name === 'children') continue;
if (name.match(/[\s\n\\/='"\0<>]/)) continue;
if (!(opts && opts.allAttributes) && (name === 'key' || name === 'ref')) continue;
if (name === 'className') {
if (attributes.class) continue;
name = 'class';
}
else if (isSvgMode && name.match(/^xlink:?./)) {
name = name.toLowerCase().replace(/^xlink:?/, 'xlink:');
}
if (name === 'style' && v && typeof v === 'object') {
v = styleObjToCss(v);
}
let hooked = opts.attributeHook && opts.attributeHook(name, v, store, opts, isComponent);
if (hooked || hooked === '') {
s += hooked;
continue;
}
if (name === 'dangerouslySetInnerHTML') {
html = v && v.__html;
}
else if ((v || v === 0 || v === '') && typeof v !== 'function') {
if (v === true || v === '') {
v = name;
// in non-xml mode, allow boolean attributes
if (!opts || !opts.xml) {
s += ' ' + name;
continue;
}
}
s += ` ${name}="${encodeEntities(v)}"`;
}
}
}
// account for >1 multiline attribute
if (pretty) {
let sub = s.replace(/^\n\s*/, ' ');
if (sub !== s && !~sub.indexOf('\n')) s = sub;
else if (pretty && ~s.indexOf('\n')) s += '\n';
}
s = `<${nodeName}${s}>`;
if (String(nodeName).match(/[\s\n\\/='"\0<>]/)) throw s;
let isVoid = String(nodeName).match(VOID_ELEMENTS);
if (isVoid) s = s.replace(/>$/, ' />');
let pieces = [];
if (html) {
// if multiline, indent.
if (pretty && isLargeString(html)) {
html = '\n' + indentChar + indent(html, indentChar);
}
s += html;
}
else if (vnode.children) {
let hasLarge = pretty && ~s.indexOf('\n');
for (let i = 0; i < vnode.children.length; i++) {
let child = vnode.children[i];
if (child != null && child !== false) {
let childSvgMode = nodeName === 'svg' ? true : nodeName === 'foreignObject' ? false : isSvgMode,
ret = renderToString(child, store, opts, childSvgMode);
if (pretty && !hasLarge && isLargeString(ret)) hasLarge = true;
if (ret) pieces.push(ret);
}
}
if (pretty && hasLarge) {
for (let i = pieces.length; i--;) {
pieces[i] = '\n' + indentChar + indent(pieces[i], indentChar);
}
}
}
if (pieces.length) {
s += pieces.join('');
}
else if (opts && opts.xml) {
return s.substring(0, s.length - 1) + ' />';
}
if (!isVoid) {
if (pretty && ~s.indexOf('\n')) s += '\n';
s += `</${nodeName}>`;
}
return s;
}
function assign(obj, props) {
for (let i in props) obj[i] = props[i];
return obj;
}
function getNodeProps(vnode) {
let props = assign({}, vnode.attributes);
props.children = vnode.children;
let defaultProps = vnode.nodeName.defaultProps;
if (defaultProps !== undefined) {
for (let i in defaultProps) {
if (props[i] === undefined) {
props[i] = defaultProps[i];
}
}
}
return props;
}

View File

@ -0,0 +1,106 @@
import h from 'omio'
import {VNode} from './vdom/vnode'
let Omi = {
x: h,
instances: {},
_instanceId: 0,
_styleId: 0,
STYLEPREFIX: '__st_',
PREFIX: '__s_',
getInstanceId: function() {
return Omi._instanceId++
},
plugins: {},
scopedStyle: true,
mapping: {},
style: {}
}
function isServer() {
return !(typeof window !== 'undefined' && window.document)
}
Omi.render = function(component, renderTo, option) {
if (isServer()) return
if(component instanceof VNode){
component = new component.tagName(component.props)
}
component.renderTo = typeof renderTo === 'string' ? document.querySelector(renderTo) : renderTo
if (typeof option === 'boolean') {
component._omi_increment = option
} else if (option) {
component._omi_increment = option.increment
component.$store = option.store
if (option.ssr) {
component.data = Object.assign({}, window.__omiSsrData, component.data)
}
}
component.install()
component.beforeRender()
component._render(true)
component._childrenInstalled(component)
component.installed()
component._execInstalledHandlers()
return component
}
function spread(vd) {
let str = ''
if (vd instanceof VNode) {
str += `<${vd.tagName} ${props2str(vd.props)}>${vd.children.map(child => {
return spread(child)
}).join('')}</${vd.tagName}>`
} else {
return vd
}
return str
}
function props2str(props) {
let result = ''
for (let key in props) {
let val = props[key]
let type = typeof val
if (type !== 'function' && type !== 'object') {
result += key + '="' + val + '" '
}
}
return result
}
function spreadStyle() {
let css = ''
for (var key in Omi.style) {
css += `\n${Omi.style[key]}\n`
}
return css
}
function stringifyData(component) {
return '<script>window.__omiSsrData=' + JSON.stringify(component.data) + '</script>'
}
Omi.renderToString = function(component, store) {
if(component instanceof VNode){
component = new component.tagName(component.props)
}
Omi.ssr = true
component.$store = store
component.install()
component.beforeRender()
component._render(true)
Omi.ssr = false
let result = `<style>${spreadStyle()}</style>\n${spread(component._virtualDom)}${stringifyData(component)}`
Omi.style = {}
Omi._instanceId = 0
return result
}
export default Omi

View File

@ -0,0 +1,18 @@
{
"name": "render-to-string",
"version": "0.0.0",
"description": "Omi SSR.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"omi",
"omio"
],
"dependencies": {
"omio": "latest"
},
"author": "dntzhang",
"license": "MIT"
}