Skip to content
This repository was archived by the owner on May 8, 2025. It is now read-only.

Commit a8ef2b3

Browse files
jerryYuXzhangyuang
authored andcommitted
feat/ssr with ts (#88)
* feat: 添加ts example
1 parent e75f3db commit a8ef2b3

35 files changed

+1456
-1
lines changed

example/ssr-with-ts/.editorconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.md]
13+
max_line_length = off
14+
trim_trailing_whitespace = false
15+
16+
[Makefile]
17+
indent_style = tab

example/ssr-with-ts/.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
logs/
2+
output/
3+
npm-debug.log
4+
yarn-error.log
5+
node_modules/
6+
package-lock.json
7+
yarn.lock
8+
coverage/
9+
dist/
10+
.idea/
11+
run/
12+
.DS_Store
13+
*.sw*
14+
*.un~
15+
.tsbuildinfo
16+
.tsbuildinfo.*

example/ssr-with-ts/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Egg + React + SSR应用骨架
2+
3+
详细用法实现请查看[官方文档](http://ykfe.net)
4+
5+
# 功能/特性
6+
7+
- [x] 基于cra脚手架开发,由cra开发的React App可无缝迁移,如果你熟悉cra的配置,上手成本几乎为0
8+
- [x] 小而美,相比于beidou,next.js这样的高度封装方案,我们的实现原理和开发模式一目了然
9+
- [x] 同时支持SSR以及CSR两种开发模式,本地开发环境以及线上环境皆可无缝切换两种渲染模式
10+
- [x] 统一前端路由与服务端路由,无需重复编写路由文件配置
11+
- [x] 支持切换路由时自动获取数据
12+
- [x] 支持本地开发HMR
13+
- [x] 稳定性经过线上大规模应用验证,可提供性能优化方案
14+
- [x] 支持tree shaking以及打包去重依赖,使得打包的bundle非常小,为同样复杂度的next.js项目的0.4倍
15+
- [x] 支持csr/ssr自定义layout,无需通过path来手动区分
16+
- [x] 抛弃传统模版引擎,拥抱 React 组件,使用JSX来作为模版
17+
- [ ] 配套[TypeScript](https://github.com/ykfe/egg-react-ssr-typescript)版本的实现
18+
- [ ] 配套serverless版本的实现

example/ssr-with-ts/build/env.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use strict'
2+
3+
const fs = require('fs')
4+
const path = require('path')
5+
const paths = require('./paths')
6+
7+
// Make sure that including paths.js after env.js will read .env variables.
8+
delete require.cache[require.resolve('./paths')]
9+
10+
const NODE_ENV = process.env.NODE_ENV
11+
if (!NODE_ENV) {
12+
throw new Error(
13+
'The NODE_ENV environment variable is required but was not specified.'
14+
)
15+
}
16+
17+
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
18+
var dotenvFiles = [
19+
`${paths.dotenv}.${NODE_ENV}.local`,
20+
`${paths.dotenv}.${NODE_ENV}`,
21+
// Don't include `.env.local` for `test` environment
22+
// since normally you expect tests to produce the same
23+
// results for everyone
24+
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
25+
paths.dotenv
26+
].filter(Boolean)
27+
28+
// Load environment variables from .env* files. Suppress warnings using silent
29+
// if this file is missing. dotenv will never modify any environment variables
30+
// that have already been set. Variable expansion is supported in .env files.
31+
// https://github.com/motdotla/dotenv
32+
// https://github.com/motdotla/dotenv-expand
33+
dotenvFiles.forEach(dotenvFile => {
34+
if (fs.existsSync(dotenvFile)) {
35+
require('dotenv-expand')(
36+
require('dotenv').config({
37+
path: dotenvFile
38+
})
39+
)
40+
}
41+
})
42+
43+
// We support resolving modules according to `NODE_PATH`.
44+
// This lets you use absolute paths in imports inside large monorepos:
45+
// https://github.com/facebook/create-react-app/issues/253.
46+
// It works similar to `NODE_PATH` in Node itself:
47+
// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
48+
// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
49+
// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims.
50+
// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
51+
// We also resolve them to make sure all tools using them work consistently.
52+
const appDirectory = fs.realpathSync(process.cwd())
53+
process.env.NODE_PATH = (process.env.NODE_PATH || '')
54+
.split(path.delimiter)
55+
.filter(folder => folder && !path.isAbsolute(folder))
56+
.map(folder => path.resolve(appDirectory, folder))
57+
.join(path.delimiter)
58+
59+
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
60+
// injected into the application via DefinePlugin in Webpack configuration.
61+
const REACT_APP = /^REACT_APP_/i
62+
63+
function getClientEnvironment (publicUrl) {
64+
const raw = Object.keys(process.env)
65+
.filter(key => REACT_APP.test(key))
66+
.reduce(
67+
(env, key) => {
68+
env[key] = process.env[key]
69+
return env
70+
},
71+
{
72+
// Useful for determining whether we’re running in production mode.
73+
// Most importantly, it switches React into the correct mode.
74+
NODE_ENV: process.env.NODE_ENV || 'development',
75+
// Useful for resolving the correct path to static assets in `public`.
76+
// For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
77+
// This should only be used as an escape hatch. Normally you would put
78+
// images into the `src` and `import` them in code to get their paths.
79+
PUBLIC_URL: publicUrl
80+
}
81+
)
82+
// Stringify all values so we can feed into Webpack DefinePlugin
83+
const stringified = {
84+
'process.env': Object.keys(raw).reduce((env, key) => {
85+
env[key] = JSON.stringify(raw[key])
86+
return env
87+
}, {})
88+
}
89+
90+
return { raw, stringified }
91+
}
92+
93+
module.exports = getClientEnvironment
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict'
2+
3+
// This is a custom Jest transformer turning style imports into empty objects.
4+
// http://facebook.github.io/jest/docs/en/webpack.html
5+
6+
module.exports = {
7+
process () {
8+
return 'module.exports = {};'
9+
},
10+
getCacheKey () {
11+
// The output is always the same.
12+
return 'cssTransform'
13+
}
14+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict'
2+
3+
const path = require('path')
4+
5+
// This is a custom Jest transformer turning file imports into filenames.
6+
// http://facebook.github.io/jest/docs/en/webpack.html
7+
8+
module.exports = {
9+
process (src, filename) {
10+
const assetFilename = JSON.stringify(path.basename(filename))
11+
12+
if (filename.match(/\.svg$/)) {
13+
return `module.exports = {
14+
__esModule: true,
15+
default: ${assetFilename},
16+
ReactComponent: (props) => ({
17+
$$typeof: Symbol.for('react.element'),
18+
type: 'svg',
19+
ref: null,
20+
key: null,
21+
props: Object.assign({}, props, {
22+
children: ${assetFilename}
23+
})
24+
}),
25+
};`
26+
}
27+
28+
return `module.exports = ${assetFilename};`
29+
}
30+
}

example/ssr-with-ts/build/paths.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict'
2+
3+
const path = require('path')
4+
const fs = require('fs')
5+
6+
// Make sure any symlinks in the project folder are resolved:
7+
// https://github.com/facebook/create-react-app/issues/637
8+
const appDirectory = fs.realpathSync(__dirname)
9+
const resolveApp = relativePath => path.resolve(appDirectory, relativePath)
10+
11+
const envPublicUrl = process.env.PUBLIC_URL
12+
13+
const getPublicUrl = appPackageJson =>
14+
envPublicUrl || require(appPackageJson).homepage
15+
16+
const moduleFileExtensions = [
17+
'web.mjs',
18+
'mjs',
19+
'web.js',
20+
'js',
21+
'web.ts',
22+
'ts',
23+
'web.tsx',
24+
'tsx',
25+
'json',
26+
'web.jsx',
27+
'jsx'
28+
]
29+
30+
// config after eject: we're in ./config/
31+
module.exports = {
32+
appPath: resolveApp('../'),
33+
appBuild: resolveApp('../output'),
34+
appSrc: resolveApp('../web'),
35+
entry: resolveApp('../web/entry'),
36+
appNodeModules: resolveApp('node_modules'),
37+
publicUrl: getPublicUrl(resolveApp('../package.json')),
38+
resolveApp: resolveApp
39+
}
40+
41+
module.exports.moduleFileExtensions = moduleFileExtensions

example/ssr-with-ts/build/util.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const paths = require('./paths')
2+
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
3+
const publicPath = paths.servedPath
4+
const shouldUseRelativeAssetPaths = publicPath === './'
5+
const isDev = process.env.NODE_ENV === 'development'
6+
const getStyleLoaders = (cssOptions, preProcessor) => {
7+
const loaders = [
8+
{
9+
loader: MiniCssExtractPlugin.loader,
10+
options: Object.assign(
11+
{},
12+
shouldUseRelativeAssetPaths ? { publicPath: '../../' } : undefined
13+
)
14+
},
15+
{
16+
loader: require.resolve('css-loader'),
17+
options: cssOptions
18+
},
19+
{
20+
loader: require.resolve('postcss-loader'),
21+
options: {
22+
ident: 'postcss',
23+
plugins: () => [
24+
require('postcss-flexbugs-fixes'),
25+
require('postcss-preset-env')({
26+
autoprefixer: {
27+
flexbox: 'no-2009'
28+
},
29+
stage: 3
30+
})
31+
]
32+
}
33+
}
34+
]
35+
if (isDev) {
36+
loaders.unshift(require.resolve('css-hot-loader'))
37+
}
38+
if (preProcessor) {
39+
// 添加额外的loader
40+
loaders.push(require.resolve(preProcessor))
41+
}
42+
return loaders
43+
}
44+
module.exports = {
45+
getStyleLoaders
46+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
'use strict'
2+
3+
const paths = require('./paths')
4+
const path = require('path')
5+
// style files regexes
6+
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
7+
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
8+
const getStyleLoaders = require('./util').getStyleLoaders
9+
const webpackModule = {
10+
strictExportPresence: true,
11+
rules: [
12+
{ parser: { requireEnsure: false } },
13+
{
14+
oneOf: [
15+
{
16+
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
17+
loader: require.resolve('url-loader'),
18+
options: {
19+
limit: 10000,
20+
name: 'static/media/[name].[hash:8].[ext]'
21+
}
22+
},
23+
{
24+
test: /\.(js|mjs|jsx|ts|tsx)$/,
25+
exclude: /node_modules/,
26+
loader: require.resolve('babel-loader'),
27+
options: {
28+
cacheDirectory: true,
29+
cacheCompression: false,
30+
presets: [
31+
[
32+
require.resolve('@babel/preset-env'),
33+
{
34+
modules: 'false'
35+
}
36+
],
37+
['react-app', { 'flow': false, 'typescript': true }]
38+
],
39+
plugins: [
40+
require.resolve('@babel/plugin-transform-runtime')
41+
]
42+
}
43+
},
44+
{
45+
test: /\.css$/,
46+
exclude: /\.module\.css$/,
47+
use: getStyleLoaders({
48+
importLoaders: 1
49+
})
50+
},
51+
{
52+
test: /\.module\.css$/,
53+
use: getStyleLoaders({
54+
importLoaders: 1,
55+
modules: true,
56+
getLocalIdent: getCSSModuleLocalIdent
57+
})
58+
},
59+
{
60+
test: /\.less$/,
61+
exclude: /\.module\.less$/,
62+
use: getStyleLoaders(
63+
{
64+
importLoaders: 2,
65+
localIdentName: '[local]'
66+
},
67+
'less-loader'
68+
),
69+
sideEffects: true
70+
},
71+
{
72+
test: /\.module\.less$/,
73+
use: getStyleLoaders(
74+
{
75+
importLoaders: 2,
76+
modules: true,
77+
getLocalIdent: getCSSModuleLocalIdent
78+
},
79+
'less-loader'
80+
)
81+
},
82+
{
83+
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
84+
loader: require.resolve('file-loader'),
85+
options: {
86+
name: 'static/media/[name].[hash:8].[ext]'
87+
}
88+
}
89+
]
90+
}
91+
]
92+
}
93+
94+
module.exports = {
95+
stats: {
96+
children: false,
97+
entrypoints: false
98+
},
99+
mode: process.env.NODE_ENV,
100+
resolve: {
101+
alias: {
102+
'@': path.resolve(__dirname, '../web')
103+
},
104+
extensions: paths.moduleFileExtensions
105+
.map(ext => `.${ext}`)
106+
},
107+
module: webpackModule,
108+
plugins: [
109+
new MiniCssExtractPlugin({
110+
filename: 'static/css/[name].css',
111+
chunkFilename: 'static/css/[name].chunk.css'
112+
})
113+
],
114+
performance: false
115+
}

0 commit comments

Comments
 (0)