提交 702b6124 编写于 作者: S Sergey Petushkov

Move all sandbox related stuff to a /sandbox; Ad SSR support; Add livereload

上级 8fb26d30
node_modules
src/decls/**/*.js
src/vendor/**/*.js
src/test/utils.js
*.test.js
example/
sandbox/
flow-typed/**/*.js
lib/
......@@ -30,21 +30,30 @@ Please also give the code of conduct a read.
## How do I set up the project?
To make development process easier, you could use a sandbox provided in this repo.
To make development process easier, you could use a sandbox provided in this repo.
To use the sandbox, follow these steps:
- First, run `npm install`. This will install all project dependencies.
1. Go to sandbox folder: `cd sandbox`
- Then, run `npm run dev`. This will start `webpack-dev-server` and you will be able to access the sandbox on `localhost:8080`.
2. Install all the dependencies: `yarn install` or `npm install`
In the sandbox `styled-components` is an alias to `./src` folder, so you can edit the source directly and dev-server will handle
rebuilding the source and reloading youur sandbox.
3. Run `yarn start` or `npm start` to start sandbox server
Now you should have the sandbox running on `localhost:3000`
In the sandbox `styled-components` is an alias to `styled-components/src` folder, so you can edit the source directly and dev-server will handle
rebuilding the source and livereloading your sandbox after the build is done.
Sandbox supports client-side and server-side rendering.
You can use an interactive editor, powered by [`react-live`](https://react-live.philpl.com/), to test your changes. But if you want more control,
you can edit the sandbox itseft. Entry point for the sandbox is located at `./sandbox/index.js`
you can edit the sandbox itseft:
- Root `<App>` componens is located at `styled-components/sandbox/src/App.js` file
- Client-side entry point is at `styled-components/sandbox/src/browser.js`
As this is just a `webpack-dev-server`, you could use its flags to change port or automatically open sandbox
in browser on start; e.g. `npm run dev -- --open` will automatically open sandbox in browser. [More info about
`webpack-dev-server` flags](https://webpack.js.org/configuration/dev-server/)
- Server-size entry point is at `styled-components/sandbox/src/server.js`
When you commit our pre-commit hook will run, which executes `lint-staged`. It will run
the linter automatically and warn you, if the code you've written doesn't comply with our
......
......@@ -28,7 +28,7 @@
"typescript": "tsc --project ./typings/tests",
"prepublish": "npm run build",
"lint-staged": "lint-staged",
"dev": "webpack-dev-server --config webpack.config.dev.js"
"dev": "babel-node example/devServer.js"
},
"repository": {
"type": "git",
......@@ -77,7 +77,6 @@
"babel-cli": "^6.22.2",
"babel-core": "^6.17.0",
"babel-eslint": "^7.1.1",
"babel-loader": "^7.1.2",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-external-helpers": "^6.22.0",
"babel-plugin-flow-react-proptypes": "^2.1.3",
......@@ -94,6 +93,7 @@
"enzyme": "^2.8.2",
"eslint": "^3.15.0",
"eslint-config-airbnb": "^13.0.0",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-flowtype-errors": "^2.0.1",
"eslint-plugin-import": "^2.2.0",
......@@ -110,7 +110,6 @@
"pre-commit": "^1.2.2",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-live": "^1.7.1",
"react-native": "^0.39.2",
"react-primitives": "^0.4.2",
"react-test-renderer": "^15.6.1",
......@@ -126,9 +125,7 @@
"rollup-plugin-uglify": "^1.0.1",
"rollup-plugin-visualizer": "^0.1.5",
"tslint": "^4.3.1",
"typescript": "^2.4.1",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.3"
"typescript": "^2.4.1"
},
"peerDependencies": {
"react": ">= 0.14.0 < 17.0.0-0"
......@@ -157,4 +154,4 @@
"threshold": "14.5kB"
}
]
}
}
\ No newline at end of file
node_modules
../src/decls/**/*.js
../src/vendor/**/*.js
{
"settings": {
"import/resolver": {
"webpack": {
// Uses webpack config aliases for import resolver checks
"config": "./webpack.config.dev.js"
}
}
}
}
\ No newline at end of file
import React, { Component } from 'react';
import { render } from 'react-dom';
import styled, { injectGlobal, css, keyframes } from 'styled-components';
import {
LiveProvider as _LiveProvider,
LiveEditor as _LiveEditor,
LiveError as _LiveError,
LivePreview as _LivePreview,
} from 'react-live';
injectGlobal`
body {
font-size: 18px;
line-height: 1.2;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-style: normal;
padding: 0;
margin: 0;
color: rgb(46, 68, 78);
-webkit-font-smoothing: subpixel-antialiased;
}
/* eslint-disable flowtype/require-valid-file-annotation */
const fs = require('fs')
button {
font-size: 18px;
}
const path = require('path')
* {
box-sizing: border-box;
}
`;
const code = `const Button = styled.button\`
border-radius: 3px;
padding: 0.25em 1em;
margin: 0 1em;
background: transparent;
color: palevioletred;
border: 2px solid palevioletred;
\${props => props.primary && css\`
background: palevioletred;
color: white;
\`}
\`;
render(
<div>
<Button>Normal Button</Button>
<Button primary>Primary Button</Button>
</div>
);`;
const Body = styled.main`
width: 100vw;
min-height: 100vh;
background-image: linear-gradient(20deg, #e6356f, #69e7f7);
padding: 0 20px;
`;
const Heading = styled.div`
padding-top: 30px;
text-align: center;
`;
const Title = styled.h1`
@media (max-width: 40.625em) {
font-size: 26px;
}
`;
const Subtitle = styled.p``;
const Content = styled.div`
width: 100%;
max-width: 860px;
margin: 0 auto;
margin-top: 60px;
`;
const Code = styled.span`
white-space: pre;
vertical-align: middle;
font-family: monospace;
display: inline-block;
background-color: #1e1f27;
color: #c5c8c6;
padding: 0.1em 0.3em 0.15em;
font-size: 0.8em;
border-radius: 0.2em;
`;
const LiveProvider = styled(_LiveProvider)`
display: flex;
flex-wrap: wrap;
border-radius: 3px;
overflow: hidden;
box-shadow: 3px 3px 18px rgba(66, 22, 93, 0.3);
`;
const LiveBlock = styled.div`
flex-basis: 50%;
width: 50%;
max-width: 50%;
padding: 0.5rem;
@media (max-width: 40.625em) {
flex-basis: auto;
width: 100%;
max-width: 100%;
const express = require('express')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpack = require('webpack')
const { CONFIG, logger } = require('./util')
const webpackConfig = require('./webpack.config.dev')
const livereloadMiddleware = require('./livereload/middleware')
const compiler = webpack(webpackConfig)
const devMiddleware = webpackDevMiddleware(compiler, webpackConfig.devServer)
const indexHtml = fs.readFileSync(
path.resolve(__dirname, './public/index.html'),
'utf-8' // eslint-disable-line comma-dangle
)
const app = express()
const server = require('http').Server(app)
app.use(devMiddleware)
app.use(livereloadMiddleware(compiler, server))
app.get('*', (req, res) => {
// We use webpackDevMiddleware in-memory file system to read server script after compilation
const serverScript = devMiddleware.fileSystem.readFileSync(
path.join(webpackConfig.output.path, 'server.js'),
'utf-8' // eslint-disable-line comma-dangle
)
// Then we evaluate this script to get prerendered content
const { html, css } = eval(serverScript) // eslint-disable-line no-eval
// Replace placeholders in index.html template
const response = indexHtml
.replace(/<!-- SSR:HTML -->/, `<div id="react-root">${html}</div>`)
.replace(/<!-- SSR:CSS -->/, css)
// Done! Send it to the client
res.send(response)
})
server.listen(CONFIG.PORT, CONFIG.HOST, err => {
if (err) {
logger.err('Failed to start sandbox:')
logger.err(err)
} else {
logger.info(
`Listening on port ${CONFIG.PORT}. Open up http://${CONFIG.HOST}:${CONFIG.PORT}/ in your browser.` // eslint-disable-line comma-dangle
)
}
`;
const LiveEditor = LiveBlock.withComponent(_LiveEditor).extend`
overflow: auto;
`;
const LivePreview = LiveBlock.withComponent(_LivePreview).extend`
background-color: white;
`;
const LiveError = styled(_LiveError)`
flex-basis: 100%;
background: #ff5555;
color: #fff;
padding: 0.5rem;
`;
const App = () => (
<Body>
<Heading>
<Title>
Interactive sandbox for <Code>styled-components</Code>
</Title>
<Subtitle>
Make changes to the <Code>./src/**</Code> and see them take effect in
realtime!
</Subtitle>
</Heading>
<Content>
<LiveProvider code={code} scope={{ styled, css, keyframes }} noInline>
<LiveEditor />
<LivePreview />
<LiveError />
</LiveProvider>
</Content>
</Body>
);
render(<App />, document.querySelector('#react-root'));
})
/* eslint-disable flowtype/require-valid-file-annotation */
import socket from 'socket.io-client'
import { EVENTS, PREFIX, logger } from '../util'
const io = socket(`${window.location.protocol}//${window.location.host}`)
const messages = {
[EVENTS.Connect]: () => {
logger.info(`${PREFIX} Connected to the livereload server`)
},
[EVENTS.Disconnect]: () => {
logger.warn(`${PREFIX} Disconnected!`)
},
[EVENTS.Error]: data => {
logger.err(`${PREFIX} Errors while compiling.`)
data.forEach(err => {
logger.err(err)
})
},
[EVENTS.Warning]: data => {
logger.warn(`${PREFIX} Warnings while compiling.`)
data.forEach(warn => {
logger.warn(warn)
})
},
[EVENTS.Done]: () => {
logger.info(`${PREFIX} Content base changed. Reloading...`)
window.location.reload()
},
[EVENTS.Invalid]: () => {
logger.info(`${PREFIX} App updated. Recompiling...`)
},
}
Object.keys(messages).forEach(msgType => io.on(msgType, messages[msgType]))
/* eslint-disable flowtype/require-valid-file-annotation */
const socket = require('socket.io')
const stripAnsi = require('strip-ansi')
const { EVENTS } = require('../util')
module.exports = (compiler, server) => {
const io = socket(server)
const stats = {
hasErrors: false,
hasWarnings: false,
json: null,
}
io.on(EVENTS.Connect, connectedSocket => {
if (stats.hasWarnings) {
connectedSocket.emit(EVENTS.Warning, stats.json.warnings.map(stripAnsi))
}
if (stats.hasErrors) {
connectedSocket.emit(EVENTS.Error, stats.json.errors.map(stripAnsi))
}
})
compiler.plugin(EVENTS.Done, webpackStats => {
io.emit(EVENTS.Done)
stats.hasErrors = webpackStats.hasErrors()
stats.hasWarnings = webpackStats.hasWarnings()
stats.json = webpackStats.toJson()
})
compiler.plugin(EVENTS.Invalid, () => {
io.emit('invalid')
})
return (req, res, next) => {
// Maybe some day we will need to hook something here
next()
}
}
{
"name": "sandbox",
"version": "0.0.0",
"main": "src/index.js",
"license": "MIT",
"scripts": {
"start": "node ./index.js"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"eslint": "^4.9.0",
"eslint-loader": "^1.9.0",
"express": "^4.16.2",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-live": "^1.7.1",
"socket.io": "^2.0.3",
"socket.io-client": "^2.0.3",
"strip-ansi": "^4.0.0",
"webpack": "^3.8.1",
"webpack-dev-middleware": "^1.12.0",
"webpack-dev-server": "^2.9.3"
},
"dependencies": {}
}
\ No newline at end of file
......@@ -10,13 +10,16 @@
<link rel="icon" type="image/png" href="https://styled-components.com/static/favicon.png">
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<!-- Load livereload script ASAP -->
<script src="./livereload.js"></script>
<!-- SSR:CSS -->
</head>
<body>
<div id="react-root"></div>
<script src="./bundle.js"></script>
<!-- SSR:HTML -->
<script src="./browser.js"></script>
</body>
</html>
\ No newline at end of file
/* eslint-disable flowtype/require-valid-file-annotation */
import React from 'react'
import styled, { css, keyframes, injectGlobal } from 'styled-components'
import {
LiveProvider as _LiveProvider,
LiveEditor as _LiveEditor,
LiveError as _LiveError,
LivePreview as _LivePreview,
} from 'react-live'
// eslint-disable-next-line no-unused-expressions
injectGlobal`
body {
font-size: 16px;
line-height: 1.2;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-style: normal;
padding: 0;
margin: 0;
color: rgb(46, 68, 78);
-webkit-font-smoothing: subpixel-antialiased;
}
* {
box-sizing: border-box;
}
`
const code = `const Button = styled.button\`
font-size: 16px;
border-radius: 5px;
padding: 0.25em 1em;
margin: 0 1em;
background: transparent;
color: palevioletred;
border: 2px solid palevioletred;
\${props => props.primary && css\`
background: palevioletred;
color: white;
\`}
\`;
render(
<div>
<Button>Normal Button</Button>
<Button primary>Primary Button</Button>
</div>
);`
const Body = styled.main`
width: 100vw;
min-width: 100vw;
min-height: 100vh;
background-image: linear-gradient(20deg, #e6356f, #69e7f7);
padding: 0 20px;
`
const Heading = styled.div`
padding-top: 30px;
text-align: center;
`
const Title = styled.h1`@media (max-width: 40.625em) {font-size: 26px;}`
const Subtitle = styled.p``
const Content = styled.div`
width: 100%;
max-width: 860px;
margin: 0 auto;
margin-top: 60px;
`
const Code = styled.span`
white-space: pre;
vertical-align: middle;
font-family: monospace;
display: inline-block;
background-color: #1e1f27;
color: #c5c8c6;
padding: 0.1em 0.3em 0.15em;
font-size: 0.8em;
border-radius: 0.2em;
`
const LiveProvider = styled(_LiveProvider)`
display: flex;
flex-wrap: wrap;
border-radius: 3px;
overflow: hidden;
box-shadow: 3px 3px 18px rgba(66, 22, 93, 0.3);
`
const LiveBlock = styled.div`
flex-basis: 50%;
width: 50%;
max-width: 50%;
padding: 0.5rem;
@media (max-width: 40.625em) {
flex-basis: auto;
width: 100%;
max-width: 100%;
}
`
const LiveEditor = LiveBlock.withComponent(_LiveEditor).extend`
overflow: auto;
`
const LivePreview = LiveBlock.withComponent(_LivePreview).extend`
background-color: white;
`
const LiveError = styled(_LiveError)`
flex-basis: 100%;
background: #ff5555;
color: #fff;
padding: 0.5rem;
`
const App = () => (
<Body>
<Heading>
<Title>
Interactive sandbox for <Code>styled-components</Code>
</Title>
<Subtitle>
Make changes to the <Code>./src</Code> and see them take effect in
realtime!
</Subtitle>
</Heading>
<Content>
<LiveProvider code={code} scope={{ styled, css, keyframes }} noInline>
<LiveEditor />
<LivePreview />
<LiveError />
</LiveProvider>
</Content>
</Body>
)
export default App
/* eslint-disable flowtype/require-valid-file-annotation */
import React from 'react'
import { hydrate } from 'react-dom'
import App from './App'
hydrate(<App />, document.querySelector('#react-root'))
/* eslint-disable flowtype/require-valid-file-annotation */
import React from 'react'
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'
import App from './App'
const sheet = new ServerStyleSheet()
export const html = renderToString(sheet.collectStyles(<App />))
export const css = sheet.getStyleTags()
/* eslint-disable flowtype/require-valid-file-annotation */
const EVENTS = {
Connect: 'connect',