Repository: ReactTraining/react-stdio Branch: master Commit: 69489252dc34 Files: 12 Total size: 13.1 KB Directory structure: gitextract_6n57gqjw/ ├── .gitignore ├── .prettierignore ├── .travis.yml ├── README.md ├── jest.config.js ├── modules/ │ ├── __mocks__/ │ │ ├── ContextComponent.js │ │ └── TestComponent.js │ ├── __tests__/ │ │ └── server-test.js │ ├── cli.js │ └── index.js ├── package.json └── scripts/ └── build.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /build/ /node_modules/ ================================================ FILE: .prettierignore ================================================ package.json ================================================ FILE: .travis.yml ================================================ language: node_js node_js: 8 cache: directories: - "$HOME/.npm" - "$HOME/.pkg-cache" env: - NPM_TAG=$([[ "$TRAVIS_TAG" == *-* ]] && echo "next" || echo "latest") script: - npm test - npm run build deploy: - provider: npm skip_cleanup: true email: npm@mjackson.me tag: "$NPM_TAG" api_key: secure: Ouhk1VI9D8JUihTJl16S2yVYHu1ngFEbmrrq6KJzTCD69N4NiyvXzMYhN97K/R0twQqnxS10aAufPvXnFtTvQz48/USGp34gSzLySSqWCXsCpOothsew4SUDqHZPdLIUJqwhpYyJsyCtDbwf8cOTgxlbXW+DE8Y1s0LTJjbbJrg2RlwNjIdYeAjfDQuO2nL3zHPAGJQ/mYJEyicZ6T2H46O4kLuzWvocvUYnEV/j/gtBtBCukg9UG//YxHJVlNfeRXYCiQ/mf5ReCY7zgghK+eJXiaKJZ+l/hV2sp7C893YwKs1IQJ5kn/liwtJfqLGSKrsw7jwu1AESx1jhQsdhCJx5pj61BUTtAGF1acetiD7/MmIHcuDUYqiVsK38UYUxW3XUIOOWH8FpcAJ3+tOpO34kDd4LCBxe09BMbDaGruuSh7+JmbBKNgvX+kJu8b4I80vRs0SrrF9lqdjQ9q/BqwnxbBWXhCZo6dDZEEwwNtLyz6hiuafnZKZgZGb9gcNQ/kwccSnZuifhnNU5nHJL9eJJrAL+XY0l8OWVOMbmuX+iSW7b0yVWqgf3bTiAez2fUUhx1bWFQlOPo/Z5I8y2zZjzCKdFOk59J4hFAhtMGkw3LJmoCyqkVGxR2x1TnH0z8R8ScBZafV8RK4/GDQnOxNIPHIPtdRn8XIXtGkev/YE= on: tags: true condition: '"$TRAVIS_TAG" =~ ^v[0-9]' - provider: releases skip_cleanup: true file: build/*.zip file_glob: true prerelease: true api_key: secure: RFzhM7HoiSqoOt79Y1Obx7RkgO4UujKLTyqgci6vln0x5A/DchUh7rErpvDLNzvrDeIIp5S41MeQ+CJuVnL5BSWvhsf50/qmSfY2iky3Gl8r5qDG0uKffwPv5Pw+TmtiS3hy9n7PY4Py6u+2fIUChK7OKbPfvvjGLNWAqUi+B9U1c9qOBkHg2LkH+XdtO4dwsjHB/mZ4s10PntLfI6mA0RXIW8/mhYD4gYTWg/DtuenNMcZKDAP+z+GWZr8cen7haMOtoYqY9SA103VgeudXpSzZr5JKSMGnx6YrHicsGaRIal69sC6RspelJsW2+6yaQtIWH8zvb9IvFPv9QP3xYEftVkqxJXr9fsDBVFZIkrF3fCBf7at8UT1CVz3kEH07oc+RZahnVBNqvTbelO32whY2wSV7QBVg6xf0UEeP+un9eBIn1M9AUXiH90KskNVu+Gcs0kNqurpo/cEXXcAuGxK/2KGyfJkg9MKx1B6FmyW1QOIS8ZGOwiUha77fudDq+PUTrjByY2NEyk1GhUmildyoGx5Ds+vwqgBHHkGu7VVitltX7SnnMh48E3AeylYHE2iMSfDRujRefOgL3sEAnHjhU3yBnfRdPsD7jdi/Rux1qzbpVdK5u7gPxrr9SHYc49zVd6e7075UC7twiV99NQoYUpI+7f2q1KffRRt+q24= on: tags: true condition: '"$TRAVIS_TAG" =~ ^v[0-9] && "$NPM_TAG" = "next"' - provider: releases skip_cleanup: true file: build/*.zip file_glob: true prerelease: false api_key: secure: RFzhM7HoiSqoOt79Y1Obx7RkgO4UujKLTyqgci6vln0x5A/DchUh7rErpvDLNzvrDeIIp5S41MeQ+CJuVnL5BSWvhsf50/qmSfY2iky3Gl8r5qDG0uKffwPv5Pw+TmtiS3hy9n7PY4Py6u+2fIUChK7OKbPfvvjGLNWAqUi+B9U1c9qOBkHg2LkH+XdtO4dwsjHB/mZ4s10PntLfI6mA0RXIW8/mhYD4gYTWg/DtuenNMcZKDAP+z+GWZr8cen7haMOtoYqY9SA103VgeudXpSzZr5JKSMGnx6YrHicsGaRIal69sC6RspelJsW2+6yaQtIWH8zvb9IvFPv9QP3xYEftVkqxJXr9fsDBVFZIkrF3fCBf7at8UT1CVz3kEH07oc+RZahnVBNqvTbelO32whY2wSV7QBVg6xf0UEeP+un9eBIn1M9AUXiH90KskNVu+Gcs0kNqurpo/cEXXcAuGxK/2KGyfJkg9MKx1B6FmyW1QOIS8ZGOwiUha77fudDq+PUTrjByY2NEyk1GhUmildyoGx5Ds+vwqgBHHkGu7VVitltX7SnnMh48E3AeylYHE2iMSfDRujRefOgL3sEAnHjhU3yBnfRdPsD7jdi/Rux1qzbpVdK5u7gPxrr9SHYc49zVd6e7075UC7twiV99NQoYUpI+7f2q1KffRRt+q24= on: tags: true condition: '"$TRAVIS_TAG" =~ ^v[0-9] && "$NPM_TAG" = "latest"' ================================================ FILE: README.md ================================================ # react-stdio [![Travis][build-badge]][build] [![npm package][npm-badge]][npm] [build-badge]: https://img.shields.io/travis/ReactTraining/react-stdio/master.svg?style=flat-square [build]: https://travis-ci.org/ReactTraining/react-stdio [npm-badge]: https://img.shields.io/npm/v/react-stdio.svg?style=flat-square [npm]: https://www.npmjs.org/package/react-stdio [react-stdio](https://npmjs.org/package/react-stdio) lets you render [React](https://reactjs.org/) components on the server, regardless of the backend technology you're using. As its name suggests, other processes communicate with react-stdio using standard streams. The protocol is JSON, so any environment that can spawn a child process and write JSON to its stdin can use the server. Requests are handled serially, so responses are issued in the same order requests are received. ## Installation If you have node installed, you can install using npm: $ npm install -g react-stdio This will put the `react-stdio` executable in your [`npm bin`](https://docs.npmjs.com/cli/bin). If you don't have node installed, you can download the executable for your architecture from [the releases page](https://github.com/ReactTraining/react-stdio/releases). ## Usage After installation, execute `react-stdio` to start the server. To render a React component, write a JSON blob to stdin with any of the following properties: component The path to a file that exports a React component (required) props Any props you want to pass to the component (optional, default is {}) render The type of rendering (optional, default is renderToString) If the request is successful, the server will put a JSON blob with `{"html":"...","context":...}` on stdout. If the request fails for some reason, the JSON will have an `error` property instead of `html`. Example: $ echo '{"component":"./MyComponent","props":{"message":"hello"}}' | react-stdio If you'd like to use a render method other than [`renderToString`](https://facebook.github.io/react/docs/top-level-api.html#reactdomserver.rendertostring) or [`renderToStaticMarkup`](https://facebook.github.io/react/docs/top-level-api.html#reactdomserver.rendertostaticmarkup) you can pass a path to a file that exports your rendering function. The signature of your `render` function should be: ```js function render(element, callback) { // ... } ``` This function is asynchronous so you have time to do data fetching before you render if you wish. Call `callback(error, html)` when you're finished. ## Environment Your component file is loaded in a vanilla node.js environment. If you need additional code transforms to run (e.g. using webpack or Browserify) you should create your bundle first and tell react-stdio to load your bundle instead of the plain component file. If you're using webpack to build your bundle, you'll want to use `"libraryTarget": "commonjs2"` in your config so the bundle exports the component using `module.exports = MyComponent`. Also, since react-stdio uses the `stdout` stream for all program output, all writes your code makes to `process.stdout` (including `console.log` statements) are redirected to `process.stderr`. ## Integrations * [Elixir/Phoenix](http://blog.overstuffedgorilla.com/render-react-with-phoenix/) * [Ruby on Rails](https://github.com/aaronvb/rails_react_stdio) If you'd like to add an integration here, please submit a PR. ## About react-stdio is developed and maintained by [React Training](https://reacttraining.com). If you're interested in learning more about what React can do for your company, please [get in touch](mailto:hello@reacttraining.com)! ================================================ FILE: jest.config.js ================================================ module.exports = { testURL: 'http://localhost/' }; ================================================ FILE: modules/__mocks__/ContextComponent.js ================================================ const React = require("react"); let context = {}; function ContextComponent() { context.test = true; return React.createElement("div", null, "I am a context component"); } ContextComponent.context = context; module.exports = ContextComponent; ================================================ FILE: modules/__mocks__/TestComponent.js ================================================ const React = require("react"); function TestComponent() { return React.createElement("div", null, "I am a test component"); } module.exports = TestComponent; ================================================ FILE: modules/__tests__/server-test.js ================================================ const path = require('path'); const childProcess = require('child_process'); function mock(name) { return path.join(__dirname, '..', '__mocks__', name); } describe('server', () => { let proc; beforeEach(() => { proc = childProcess.spawn(path.join(__dirname, '..', 'cli.js'), { stdio: 'pipe' }); }); afterEach(() => { proc.kill(); }); it('throws an error when component is missing', done => { proc.stdin.write(JSON.stringify({})); proc.stdout.once('data', out => { expect(JSON.parse(out).error).toEqual('Missing { component } in request'); done(); }); }); it('throws an error when component cannot be found', done => { proc.stdin.write(JSON.stringify({ component: 'component.js' })); proc.stdout.once('data', out => { expect(JSON.parse(out).error).toEqual( 'Cannot load component: component.js' ); done(); }); }); it('renders the component', done => { proc.stdin.write( JSON.stringify({ component: mock('TestComponent.js') }) ); proc.stdout.once('data', out => { expect(JSON.parse(out).html).toMatch('I am a test component'); done(); }); }); it('renders a component and exposes additional context', done => { proc.stdin.write( JSON.stringify({ component: mock('ContextComponent.js') }) ); proc.stdout.once('data', out => { const result = JSON.parse(out); expect(result.html).toMatch('I am a context component'); expect(result.context).toEqual({ test: true }); done(); }); }); }); ================================================ FILE: modules/cli.js ================================================ #!/usr/bin/env node const EventStream = require("event-stream"); const JSONStream = require("JSONStream"); const createRequestHandler = require("./index").createRequestHandler; // Redirect stdout to stderr, but save a reference so we can // still write to stdout. const stdout = process.stdout; Object.defineProperty(process, "stdout", { configurable: true, enumerable: true, value: process.stderr }); // Ensure console.log knows about the new stdout. const Console = require("console").Console; Object.defineProperty(global, "console", { configurable: true, enumerable: true, value: new Console(process.stdout, process.stderr) }); // Read JSON blobs from stdin, pipe output to stdout. process.stdin .pipe(JSONStream.parse()) .pipe(EventStream.map(createRequestHandler(process.cwd()))) .pipe(stdout); ================================================ FILE: modules/index.js ================================================ const path = require("path"); const invariant = require("invariant"); const ReactDOMServer = require("react-dom/server"); const React = require("react"); function loadModule(moduleId) { // Clear the require cache, in case the file was // changed since the server was started. const cacheKey = require.resolve(moduleId); delete require.cache[cacheKey]; const moduleExports = require(moduleId); // Return exports.default if using ES modules. if (moduleExports && moduleExports.default) { return moduleExports.default; } return moduleExports; } function renderToStaticMarkup(element, callback) { callback(null, ReactDOMServer.renderToStaticMarkup(element)); } function renderToString(element, callback) { callback(null, ReactDOMServer.renderToString(element)); } function handleRequest(workingDir, request, callback) { const componentPath = request.component; const renderMethod = request.render; const props = request.props; invariant(componentPath != null, "Missing { component } in request"); let render; if (renderMethod == null || renderMethod === "renderToString") { render = renderToString; } else if (renderMethod === "renderToStaticMarkup") { render = renderToStaticMarkup; } else { const methodFile = path.resolve(workingDir, renderMethod); try { render = loadModule(methodFile); } catch (error) { if (error.code !== "MODULE_NOT_FOUND") { process.stderr.write(error.stack + "\n"); } } } invariant( typeof render === "function", "Cannot load render method: %s", renderMethod ); const componentFile = path.resolve(workingDir, componentPath); let component; try { component = loadModule(componentFile); } catch (error) { if (error.code !== "MODULE_NOT_FOUND") { process.stderr.write(error.stack + "\n"); } } invariant(component != null, "Cannot load component: %s", componentPath); render(React.createElement(component, props), function(err, html) { callback(err, html, component.context); }); } function createRequestHandler(workingDir) { return function(request, callback) { try { handleRequest(workingDir, request, function(error, html, context) { if (error) { callback(error); } else if (typeof html !== "string") { // Crash the server process. callback(new Error("Render method must return a string")); } else { callback(null, JSON.stringify({ html: html, context: context })); } }); } catch (error) { callback(null, JSON.stringify({ error: error.message })); } }; } module.exports = { createRequestHandler }; ================================================ FILE: package.json ================================================ { "name": "react-stdio", "version": "3.4.8", "description": "Render React.js components on any backend", "author": "Michael Jackson", "license": "MIT", "bin": { "react-stdio": "./modules/cli.js" }, "files": [ "modules/cli.js", "modules/index.js" ], "preferGlobal": true, "repository": "ReactTraining/react-stdio", "scripts": { "build": "./scripts/build.sh", "clean": "git clean -fdX .", "test": "jest" }, "dependencies": { "JSONStream": "^1.0.7", "event-stream": "3.3.4", "invariant": "^2.2.0", "react": "^16.2.0", "react-dom": "^16.2.0" }, "devDependencies": { "jest": "^22.1.4", "pkg": "^4.3.4" }, "jest": { "testPathIgnorePatterns": [ "/node_modules/", "__tests__/mocks" ] } } ================================================ FILE: scripts/build.sh ================================================ #!/bin/bash mkdir -p build tag=${TRAVIS_TAG:-"v$npm_package_version"} # TODO: Enable x86 builds when https://github.com/zeit/pkg/issues/310 is fixed platforms=( win-x64 linux-x64 macos ) for platform in "${platforms[@]}" do archive=react-stdio-$tag-$platform echo "Creating $archive build..." pkg modules/cli.js -t $platform -o build/$archive/react-stdio echo "$tag" > build/$archive/version cd build zip -q -r $archive.zip $archive cd - > /dev/null done