omio - add rendeToString
This commit is contained in:
parent
d0afe99bdd
commit
21e8bbcb3a
|
@ -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
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
|
||||
<head></head>
|
||||
|
||||
<body>
|
||||
<script src="b.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -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 />))
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
|
||||
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;
|
||||
}
|
|
@ -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
|
|
@ -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"
|
||||
}
|
Loading…
Reference in New Issue