diff --git a/package.json b/package.json index ea2a6310..14891fb2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "preact-render-to-string", + "name": "@yr/preact-render-to-string", "amdName": "preactRenderToString", - "version": "3.6.3", + "version": "4.0.2", "description": "Render JSX to an HTML string, with support for Preact components.", "main": "dist/index.js", "jsnext:main": "src/index.js", @@ -20,15 +20,15 @@ "universal", "isomorphic" ], - "author": "Jason Miller ", + "author": "Jason Miller , Simen Sægrov ", "license": "MIT", "typings": "src/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/developit/preact-render-to-string.git" + "url": "https://github.com/yr/preact-render-to-string.git" }, "bugs": { - "url": "https://github.com/developit/preact-render-to-string/issues" + "url": "https://github.com/yr/preact-render-to-string/issues" }, "homepage": "https://github.com/developit/preact-render-to-string", "peerDependencies": { diff --git a/src/index.d.ts b/src/index.d.ts index c69419c6..0bc7e113 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -5,10 +5,12 @@ declare module render { shallow:boolean; xml:boolean; pretty:boolean; + alwaysRenderedComponents: Array[String]; } function render(vnode:VNode, context?:any, options?:Options):string; function shallowRender(vnode:VNode, context?:any):string; + function mixedRender(vnode: VNode, alwaysRenderedComponents: Array[String], context?: any): string; } export = render; diff --git a/src/index.js b/src/index.js index b2de14cd..090dd101 100644 --- a/src/index.js +++ b/src/index.js @@ -48,12 +48,27 @@ renderToString.render = renderToString; let shallowRender = (vnode, context) => renderToString(vnode, context, SHALLOW); +/** + * Only render elements, leaving Components inline as `` + * except those specified in fullyRenderedComponents. + * This method is just a convenience alias for `render(vnode, context, { shallow:true, alwaysRenderedComponented: [] })` + * @param {VNode} vnode JSX VNode to render. + * @param {Array} alwaysRenderedComponents List of components that should be rendered with shallow rendering + * @param {Object} [context={}] Optionally pass an initial context object through the render path. + */ +let mixedRender = (vnode, alwaysRenderedComponents = [], context) => { + const opts = Object.assign({ alwaysRenderedComponents }, SHALLOW); + return renderToString(vnode, context, opts); +}; + + /** The default export is an alias of `render()`. */ export default function renderToString(vnode, context, opts, inner, isSvgMode) { let { nodeName, attributes, children } = vnode || EMPTY, isComponent = false; context = context || {}; opts = opts || {}; + opts.alwaysRenderedComponents = opts.alwaysRenderedComponents || []; let pretty = opts.pretty, indentChar = typeof pretty==='string' ? pretty : '\t'; @@ -69,9 +84,12 @@ export default function renderToString(vnode, context, opts, inner, isSvgMode) { // components if (typeof nodeName==='function') { + let componentName = getComponentName(nodeName); isComponent = true; - if (opts.shallow && (inner || opts.renderRootComponent===false)) { - nodeName = getComponentName(nodeName); + if (opts.shallow && + (inner || opts.renderRootComponent===false) && + !opts.alwaysRenderedComponents.includes(componentName)) { + nodeName = componentName; } else { let props = getNodeProps(vnode), @@ -230,10 +248,12 @@ function getFallbackComponentName(component) { return name; } renderToString.shallowRender = shallowRender; +renderToString.mixedRender = mixedRender; export { renderToString as render, renderToString, - shallowRender + shallowRender, + mixedRender }; diff --git a/test/mixedRender.js b/test/mixedRender.js new file mode 100644 index 00000000..e2d543fd --- /dev/null +++ b/test/mixedRender.js @@ -0,0 +1,84 @@ +import { mixedRender } from '../src'; +import { h, Component } from 'preact'; +import chai, { expect } from 'chai'; +import { spy, match } from 'sinon'; +import sinonChai from 'sinon-chai'; +chai.use(sinonChai); + +describe('mixedRender()', () => { + it('should not render nested components when not white listed', () => { + let Test = spy( ({ foo, children }) =>
test child{ children }
); + Test.displayName = 'Test'; + + let rendered = mixedRender( +
+ asdf +
+ ); + + expect(rendered).to.equal(`
asdf
`); + expect(Test).not.to.have.been.called; + }); + + it('should always render root component', () => { + let Test = spy( ({ foo, children }) =>
test child{ children }
); + Test.displayName = 'Test'; + + let rendered = mixedRender( + + asdf + + ); + + expect(rendered).to.equal(`
test childasdf
`); + expect(Test).to.have.been.calledOnce; + }); + + it('should render nested components when they are white listed', () => { + let Test = spy( ({ foo, children }) =>
test child{ children }
); + Test.displayName = 'Test'; + + let rendered = mixedRender( +
+ asdf +
+ , ['Test']); + + expect(rendered).to.equal(`
test childasdf
`); + expect(Test).to.have.been.called; + }); + + it('should not render nested components inside a whitelisted component', () => { + let Test = spy( ({ foo, children }) =>
test child{ children }
); + let Ignored = spy( ({ title, children }) =>

This {title} should not be rendered

); + Test.displayName = 'Test'; + Ignored.displayName = 'Ignored'; + + let rendered = mixedRender( +
+ +
+ , ['Test']); + + expect(rendered).to.equal(`
test child
`); + expect(Test).to.have.been.called; + expect(Ignored).to.not.have.been.called; + }); + + it('should render deeply nested components when all are white listed', () => { + let Test = spy( ({ foo, children }) =>
test child{ children }
); + let Ignored = spy( ({ title, children }) =>

This {title} should be rendered

); + Test.displayName = 'Test'; + Ignored.displayName = 'Ignored'; + + let rendered = mixedRender( +
+ +
+ , ['Test', 'Ignored']); + + expect(rendered).to.equal(`
test child

This FooBarTitle should be rendered

`); + expect(Test).to.have.been.called; + expect(Ignored).to.have.been.called; + }); +});