Skip to content

Commit

Permalink
feat: 新增ssr/csr结合loadable示例 (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangyuang committed Jun 28, 2019
1 parent 854dbf1 commit 219e71a
Show file tree
Hide file tree
Showing 42 changed files with 1,765 additions and 11 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ module.exports = {
],
template: resolvePath('web/index.html'), // 使用的模版文件路径
injectCss: (chunkName) => ([
`/static/css/${chunkName}.chunk.css`
`<link rel='stylesheet' href='/static/css/${chunkName}.chunk.css' />`
]), // 客户端需要加载的静态css文件资源
injectScript: (chunkName) => ([
`<script src='/static/js/runtime~${chunkName}.js'></script>`,
Expand Down
2 changes: 1 addition & 1 deletion example/ssr-with-antd/config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = {
baseDir: resolvePath(''),
template: resolvePath('web/index.html'), // 使用的模版文件路径
injectCss: (chunkName) => ([
`/static/css/${chunkName}.chunk.css`
`<link rel='stylesheet' href='/static/css/${chunkName}.chunk.css' />`
]), // 客户端需要加载的静态样式表
injectScript: (chunkName) => ([
`<script src='/static/js/runtime~${chunkName}.js'></script>`,
Expand Down
2 changes: 1 addition & 1 deletion example/ssr-with-js/config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = {
baseDir: resolvePath(''),
template: resolvePath('web/index.html'), // 使用的模版文件路径
injectCss: (chunkName) => ([
`/static/css/${chunkName}.chunk.css`
`<link rel='stylesheet' href='/static/css/${chunkName}.chunk.css' />`
]), // 客户端需要加载的静态样式表
injectScript: (chunkName) => ([
`<script src='/static/js/runtime~${chunkName}.js'></script>`,
Expand Down
2 changes: 1 addition & 1 deletion example/ssr-with-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "egg-ssr",
"version": "1.1.2",
"version": "1.1.3",
"dependencies": {
"egg": "^2.21.0",
"egg-bin": "^4.13.1",
Expand Down
71 changes: 71 additions & 0 deletions example/ssr-with-loadable/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# next.js build output
.next

.DS_Store

.vscode

run/

dist/

package-lock.json
33 changes: 33 additions & 0 deletions example/ssr-with-loadable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Egg + React + SSR应用骨架

详细用法实现请查看[官方文档](http://ykfe.net)

# Getting Start

这里我们提供了一个脚手架来方便你创建项目

```
$ npm install yk-cli -g
$ ykcli init <Your Project Name>
$ cd <Your Project Name>
$ npm i
$ npm start
$ open http://localhost:7001
```

# 功能/特性

- [x] 基于cra脚手架开发,由cra开发的React App可无缝迁移,如果你熟悉cra的配置,上手成本几乎为0
- [x] 小而美,相比于beidou,next.js这样的高度封装方案,我们的实现原理和开发模式一目了然
- [x] 同时支持SSR以及CSR两种开发模式,本地开发环境以及线上环境皆可无缝切换两种渲染模式
- [x] 统一前端路由与服务端路由,无需重复编写路由文件配置
- [x] 支持切换路由时自动获取数据
- [x] 支持本地开发HMR
- [x] 稳定性经过线上大规模应用验证,可提供性能优化方案
- [x] 支持tree shaking以及打包去重依赖,使得打包的bundle非常小,为同样复杂度的next.js项目的0.4倍
- [x] 支持csr/ssr自定义layout,无需通过path来手动区分
- [ ] 配套[TypeScript](https://github.com/ykfe/egg-react-ssr-typescript)版本的实现
- [ ] 配套serverless版本的实现



4 changes: 4 additions & 0 deletions example/ssr-with-loadable/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

module.exports = app => {

}
20 changes: 20 additions & 0 deletions example/ssr-with-loadable/app/controller/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

const Controller = require('egg').Controller
const { renderToStream } = require('ykfe-utils')

class PageController extends Controller {
async index () {
const { ctx } = this
try {
// Page为webpack打包的chunkName,项目默认的entry为Page
ctx.type = 'text/html'
ctx.status = 200
const stream = await renderToStream(ctx, 'Page', ctx.app.config)
ctx.body = stream
} catch (error) {
ctx.logger.error(`Page Controller renderToStream Error ${error}`)
}
}
}

module.exports = PageController
8 changes: 8 additions & 0 deletions example/ssr-with-loadable/app/router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict'

module.exports = app => {
const { router, controller } = app
app.config.routes.map(route => {
router.get(`${route.path}`, controller[route.controller][route.handler])
})
}
93 changes: 93 additions & 0 deletions example/ssr-with-loadable/build/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict'

const fs = require('fs')
const path = require('path')
const paths = require('./paths')

// Make sure that including paths.js after env.js will read .env variables.
delete require.cache[require.resolve('./paths')]

const NODE_ENV = process.env.NODE_ENV
if (!NODE_ENV) {
throw new Error(
'The NODE_ENV environment variable is required but was not specified.'
)
}

// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
var dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`,
`${paths.dotenv}.${NODE_ENV}`,
// Don't include `.env.local` for `test` environment
// since normally you expect tests to produce the same
// results for everyone
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
paths.dotenv
].filter(Boolean)

// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set. Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile
})
)
}
})

// We support resolving modules according to `NODE_PATH`.
// This lets you use absolute paths in imports inside large monorepos:
// https://github.com/facebook/create-react-app/issues/253.
// It works similar to `NODE_PATH` in Node itself:
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
// We also resolve them to make sure all tools using them work consistently.
const appDirectory = fs.realpathSync(process.cwd())
process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter)
.filter(folder => folder && !path.isAbsolute(folder))
.map(folder => path.resolve(appDirectory, folder))
.join(path.delimiter)

// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
// injected into the application via DefinePlugin in Webpack configuration.
const REACT_APP = /^REACT_APP_/i

function getClientEnvironment (publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
env[key] = process.env[key]
return env
},
{
// Useful for determining whether we’re running in production mode.
// Most importantly, it switches React into the correct mode.
NODE_ENV: process.env.NODE_ENV || 'development',
// Useful for resolving the correct path to static assets in `public`.
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
// This should only be used as an escape hatch. Normally you would put
// images into the `src` and `import` them in code to get their paths.
PUBLIC_URL: publicUrl
}
)
// Stringify all values so we can feed into Webpack DefinePlugin
const stringified = {
'process.env': Object.keys(raw).reduce((env, key) => {
env[key] = JSON.stringify(raw[key])
return env
}, {})
}

return { raw, stringified }
}

module.exports = getClientEnvironment
14 changes: 14 additions & 0 deletions example/ssr-with-loadable/build/jest/cssTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

// This is a custom Jest transformer turning style imports into empty objects.
// http://facebook.github.io/jest/docs/en/webpack.html

module.exports = {
process () {
return 'module.exports = {};'
},
getCacheKey () {
// The output is always the same.
return 'cssTransform'
}
}
30 changes: 30 additions & 0 deletions example/ssr-with-loadable/build/jest/fileTransform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'

const path = require('path')

// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html

module.exports = {
process (src, filename) {
const assetFilename = JSON.stringify(path.basename(filename))

if (filename.match(/\.svg$/)) {
return `module.exports = {
__esModule: true,
default: ${assetFilename},
ReactComponent: (props) => ({
$$typeof: Symbol.for('react.element'),
type: 'svg',
ref: null,
key: null,
props: Object.assign({}, props, {
children: ${assetFilename}
})
}),
};`
}

return `module.exports = ${assetFilename};`
}
}
71 changes: 71 additions & 0 deletions example/ssr-with-loadable/build/paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict'

const path = require('path')
const fs = require('fs')
const url = require('url')

// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebook/create-react-app/issues/637
const appDirectory = fs.realpathSync(process.cwd())
const resolveApp = relativePath => path.resolve(appDirectory, relativePath)

const envPublicUrl = process.env.PUBLIC_URL

function ensureSlash (inputPath, needsSlash) {
const hasSlash = inputPath.endsWith('/')
if (hasSlash && !needsSlash) {
return inputPath.substr(0, inputPath.length - 1)
} else if (!hasSlash && needsSlash) {
return `${inputPath}/`
} else {
return inputPath
}
}

const getPublicUrl = appPackageJson =>
envPublicUrl || require(appPackageJson).homepage

// We use `PUBLIC_URL` environment variable or "homepage" field to infer
// "public path" at which the app is served.
// Webpack needs to know it to put the right <script> hrefs into HTML even in
// single-page apps that may serve index.html for nested URLs like /todos/42.
// We can't use a relative path in HTML because we don't want to load something
// like /todos/42/static/js/bundle.7289d.js. We have to know the root.
function getServedPath (appPackageJson) {
const publicUrl = getPublicUrl(appPackageJson)
const servedUrl =
envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/')
return ensureSlash(servedUrl, true)
}

const moduleFileExtensions = [
'web.mjs',
'mjs',
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx'
]

// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp('.env'),
appPath: resolveApp('.'),
appBuild: resolveApp('dist'),
appPublic: resolveApp('app/public'),
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('web'),
entry: resolveApp('web/entry'),
appNodeModules: resolveApp('node_modules'),
publicUrl: getPublicUrl(resolveApp('package.json')),
servedPath: getServedPath(resolveApp('package.json')),
template: resolveApp('web/index.html'),
resolveApp: resolveApp
}

module.exports.moduleFileExtensions = moduleFileExtensions
Loading

0 comments on commit 219e71a

Please sign in to comment.