├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README-zh.md ├── README.md ├── core ├── README.md ├── bin │ └── ssr ├── package.json ├── src │ ├── index.ts │ ├── interface.ts │ ├── overrides │ │ ├── env.ts │ │ ├── index.ts │ │ ├── overridesDevServer.ts │ │ ├── overridesWebpackDevServerUtils.ts │ │ ├── path.ts │ │ └── pathUtils.ts │ ├── script │ │ ├── build.ts │ │ └── start.ts │ └── utils │ │ ├── ProcessingConfig.ts │ │ ├── getWebpackConfig.ts │ │ ├── index.ts │ │ ├── module │ │ └── index.ts │ │ ├── output │ │ └── index.ts │ │ └── plugins │ │ ├── CreateTemporaryAsset.ts │ │ ├── devServer.ts │ │ └── index.ts └── tsconfig.json ├── example ├── basic-plugins │ ├── .gitignore │ ├── .kktrc.ts │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── app │ │ │ ├── App.module.css │ │ │ ├── App.test.tsx │ │ │ ├── App.tsx │ │ │ └── app.css │ │ ├── index.tsx │ │ ├── server.ts │ │ └── serverIndex.tsx │ └── tsconfig.json ├── basic-routes-rematch-new │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── client.tsx │ │ ├── models │ │ │ ├── demo.ts │ │ │ ├── index.ts │ │ │ └── login.ts │ │ ├── routes │ │ │ ├── About │ │ │ │ └── index.tsx │ │ │ ├── Home │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── server.ts │ │ └── serverIndex.tsx │ └── tsconfig.json ├── basic-routes │ ├── .gitignore │ ├── .kktssrrc.ts │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── client.tsx │ │ ├── routes │ │ │ ├── About │ │ │ │ └── index.tsx │ │ │ ├── Home │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── server.ts │ │ └── serverIndex.tsx │ └── tsconfig.json ├── basic │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── robots.txt │ ├── src │ │ ├── app │ │ │ ├── App.module.css │ │ │ ├── App.test.tsx │ │ │ ├── App.tsx │ │ │ └── app.css │ │ ├── client.tsx │ │ ├── server.ts │ │ └── serverIndex.tsx │ ├── tsconfig.json │ └── typings.d.ts └── react-router-rematch-old │ ├── .gitignore │ ├── .kktssrrc.ts │ ├── README.md │ ├── mocker │ └── index.js │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── robots.txt │ ├── src │ ├── client.tsx │ ├── components │ │ └── Container │ │ │ ├── index.css │ │ │ ├── index.module.css │ │ │ ├── index.tsx │ │ │ └── react.svg │ ├── models │ │ ├── about.ts │ │ ├── global.ts │ │ ├── home.ts │ │ └── index.ts │ ├── routes │ │ ├── about │ │ │ ├── index.css │ │ │ └── index.tsx │ │ ├── home │ │ │ ├── home.module.css │ │ │ └── index.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── notmatch │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── repos │ │ │ ├── avatar.png │ │ │ ├── detail │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ │ ├── home.module.css │ │ │ └── index.tsx │ │ └── username │ │ │ ├── index.css │ │ │ └── index.tsx │ ├── server.ts │ ├── serverIndex.tsx │ ├── store │ │ └── index.ts │ ├── typings.d.ts │ └── utils │ │ ├── history.ts │ │ └── request.ts │ └── tsconfig.json ├── lerna.json ├── package.json ├── packages ├── create-kkt-ssr │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── cli.ts │ │ └── utils.ts │ ├── test │ │ └── cli.test.ts │ └── tsconfig.json ├── kkt-plugin-less │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── kkt-plugin-ssr │ ├── README.md │ ├── package.json │ ├── src │ │ ├── childCompiler.ts │ │ ├── dev.ts │ │ ├── index.ts │ │ ├── processAssets.ts │ │ └── utils │ │ │ ├── index.ts │ │ │ ├── module.ts │ │ │ └── plugins.ts │ └── tsconfig.json ├── react-ssr-enhanced │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Document.tsx │ │ ├── RoutersController.tsx │ │ ├── ensureReady.ts │ │ ├── index.ts │ │ ├── interface.ts │ │ ├── loadInitialProps.ts │ │ └── render.tsx │ └── tsconfig.json └── ssr-render │ ├── package.json │ ├── src │ └── index.tsx │ └── 思路.md ├── renovate.json ├── script ├── createjs.js └── zip.js └── test └── createjs.test.ts /.editorconfig: -------------------------------------------------------------------------------- 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 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | coverage 4 | dist 5 | .DS_Store 6 | .cache 7 | .vscode 8 | yarn.lock 9 | package-lock.json 10 | /examplejs 11 | /zip 12 | /build 13 | *.bak 14 | *.tem 15 | *.temp 16 | #.swp 17 | *.*~ 18 | ~*.* 19 | 20 | lib 21 | esm 22 | 23 | build 24 | 25 | dist -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | examples/website/build 6 | package.json 7 | dist 8 | lib 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-present, Kenny Wong . 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kktjs/ssr/e915732fed0e15ede971af4e75e1af1283e6dba8/README-zh.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | core/README.md -------------------------------------------------------------------------------- /core/bin/ssr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/index'); 4 | -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kkt/ssr", 3 | "version": "3.1.13", 4 | "description": "", 5 | "license": "MIT", 6 | "main": "lib/index.js", 7 | "module": "esm/index.js", 8 | "types": "lib/index.d.ts", 9 | "bin": { 10 | "kkt-ssr": "bin/ssr" 11 | }, 12 | "scripts": { 13 | "build": "tsbb build", 14 | "watch": "tsbb watch" 15 | }, 16 | "files": [ 17 | "lib", 18 | "esm", 19 | "src" 20 | ], 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "keywords": [], 25 | "devDependencies": { 26 | "@types/webpack-node-externals": "^2.5.3" 27 | }, 28 | "dependencies": { 29 | "@babel/core": "^7.17.8", 30 | "@babel/register": "^7.17.7", 31 | "@babel/runtime": "^7.17.8", 32 | "babel-preset-react-app": "^10.0.1", 33 | "css-loader": "^6.7.1", 34 | "kkt": "~7.1.5", 35 | "mini-css-extract-plugin": "2.6.0", 36 | "mocker-api": "^2.9.5", 37 | "nodemon-webpack-plugin": "^4.7.1", 38 | "react-scripts": "^5.0.0", 39 | "ts-node": "^10.7.0", 40 | "typescript": "^4.6.2", 41 | "webpack-manifest-plugin": "^5.0.0", 42 | "webpack-node-externals": "^3.0.0", 43 | "webpackbar": "^5.0.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | process.env.FAST_REFRESH = 'false'; 3 | process.env.BUILD_PATH = "dist" 4 | 5 | import minimist from 'minimist'; 6 | import { BuildArgs } from 'kkt'; 7 | import { OptionsProps } from "./interface" 8 | import { getBoolean } from "./utils" 9 | 10 | function help() { 11 | const { version } = require('../package.json'); 12 | console.log('\n Usage: \x1b[34;1mkkt-ssr\x1b[0m [build|start] [--help|h]'); 13 | console.log('\n Displays help information.'); 14 | console.log('\n Options:\n'); 15 | console.log(' --version, -v ', 'Show version number'); 16 | console.log(' --help, -h ', 'Displays help information.'); 17 | console.log(' --s-ne, --s-nodeExternals ', 'server use webpack-node-external .'); 18 | console.log(' --c-ne, --c-nodeExternals ', 'client use webpack-node-external .'); 19 | console.log(' --s-st, --s-split ', 'server Split code .'); 20 | console.log(' --c-st, --c-split ', 'client Split code .'); 21 | console.log(' -o, --original ', 'Use original react-scripts config .'); 22 | console.log(' -m, --minify ', 'All Minify output.'); 23 | console.log(' --s-m, --s-minify ', 'server Minify output.'); 24 | console.log(' --c-m, --c-minify ', 'clinet Minify output.'); 25 | console.log(' --target ', 'clent or server or all.'); 26 | 27 | console.log('\n Example:\n'); 28 | console.log(' $ \x1b[35mkkt-ssr\x1b[0m build'); 29 | console.log(' $ \x1b[35mkkt-ssr\x1b[0m start'); 30 | console.log(' $ \x1b[35mkkt-ssr\x1b[0m build --s-ne'); 31 | console.log(' $ \x1b[35mkkt-ssr\x1b[0m start --s-ne'); 32 | console.log(' $ \x1b[35mkkt-ssr\x1b[0m build --s-st'); 33 | console.log(' $ \x1b[35mkkt-ssr\x1b[0m start --s-st'); 34 | console.log(' $ \x1b[35mkkt-ssr\x1b[0m start -o'); 35 | console.log(`\n \x1b[34;1m@kkt/ssr\x1b[0m \x1b[32;1mv${version || ''}\x1b[0m\n`); 36 | } 37 | 38 | interface SSRNCCArgs extends BuildArgs { 39 | "s-ne"?: boolean; 40 | "s-m"?: boolean; 41 | "s-minify"?: boolean; 42 | "s-nodeExternals"?: boolean, 43 | "s-st"?: boolean, 44 | "s-split"?: boolean, 45 | "c-ne"?: boolean; 46 | "c-nodeExternals"?: boolean, 47 | "c-st"?: boolean, 48 | "c-split"?: boolean, 49 | "c-m"?: boolean; 50 | "c-minify"?: boolean; 51 | "o"?: boolean, 52 | "original"?: boolean, 53 | "m"?: boolean; 54 | "minify"?: boolean, 55 | "target"?: "client" | "server" | "all" 56 | } 57 | 58 | (async () => { 59 | 60 | try { 61 | const args = process.argv.slice(2); 62 | const argvs: SSRNCCArgs = minimist(args); 63 | if (argvs.h || argvs.help) { 64 | return help(); 65 | } 66 | if (argvs.v || argvs.version) { 67 | const { version } = require('../package.json'); 68 | console.log(`\n \x1b[34;1m@kkt/ssr\x1b[0m \x1b[32;1mv${version || ''}\x1b[0m\n`); 69 | return; 70 | } 71 | 72 | const scriptName = argvs._[0]; 73 | 74 | const clientNodeExternals = getBoolean(argvs["c-ne"], argvs['c-nodeExternals']) 75 | const serverNodeExternals = getBoolean(argvs["s-ne"], argvs['s-nodeExternals']) 76 | 77 | const clientIsChunk = getBoolean(argvs["c-st"], argvs['c-split']) 78 | const serverIsChunk = getBoolean(argvs["s-st"], argvs['s-split']) 79 | 80 | // 使用原始 react-scripts 81 | const original = getBoolean(argvs["o"], argvs["original"]) 82 | 83 | const mini = getBoolean(argvs["m"], argvs["minify"], true) 84 | 85 | const miniServer = getBoolean(argvs["s-m"], argvs["s-minify"], mini) 86 | const miniClient = getBoolean(argvs["c-m"], argvs["c-minify"], mini) 87 | 88 | const target = argvs["target"] || "all" 89 | 90 | const options: OptionsProps = { 91 | clientNodeExternals, 92 | serverNodeExternals, 93 | clientIsChunk, 94 | serverIsChunk, 95 | original, 96 | mini, 97 | miniServer, 98 | miniClient, 99 | target 100 | } 101 | // 解决 使用 react-scripts 原始情况下 PUBLIC_URL 报错 102 | if (!Reflect.has(process.env || {}, "PUBLIC_URL")) { 103 | process.env.PUBLIC_URL = ''; 104 | } 105 | 106 | const setEnv = (NODE_ENV: "production" | "development") => { 107 | process.env.BABEL_ENV = NODE_ENV; 108 | process.env.NODE_ENV = NODE_ENV; 109 | } 110 | 111 | if (scriptName === 'build') { 112 | setEnv("production") 113 | 114 | const build = await import("./script/build") 115 | await build.default(options) 116 | 117 | } else if (scriptName === 'start') { 118 | 119 | setEnv("development") 120 | const start = await import("./script/start") 121 | await start.default(options) 122 | 123 | } 124 | } catch (error) { 125 | console.log('\x1b[31m KKT:SSR:ERROR:\x1b[0m', error); 126 | } 127 | })(); 128 | -------------------------------------------------------------------------------- /core/src/interface.ts: -------------------------------------------------------------------------------- 1 | import webpack from "webpack" 2 | import { Application } from 'express'; 3 | 4 | import { MockerOption } from "mocker-api" 5 | 6 | export type Paths = { 7 | dotenv: string; 8 | appPath: string; 9 | appBuild: string; 10 | appPublic: string; 11 | appHtml: string; 12 | appIndexJs: string; 13 | appPackageJson: string; 14 | appTsBuildInfoFile: string 15 | /** App Root Path */ 16 | appSrc: string; 17 | appTsConfig: string; 18 | appJsConfig: string; 19 | yarnLockFile: string; 20 | testsSetup: string; 21 | proxySetup: string; 22 | appNodeModules: string; 23 | swSrc: string; 24 | publicUrlOrPath: string; 25 | // These properties only exist before ejecting: 26 | ownPath: string; 27 | ownNodeModules: string; 28 | appTypeDeclarations: string; 29 | ownTypeDeclarations: string; 30 | moduleFileExtensions?: string[] 31 | }; 32 | 33 | export type Options = Omit & OptionsProps & { 34 | paths: Partial> 35 | } 36 | 37 | export type WebpackConfigFunction = (conf: webpack.Configuration[], env: "development" | "production", options: Options) => webpack.Configuration[] | webpack.Configuration; 38 | 39 | export interface OverridesProps { 40 | env?: Record, 41 | /** 是否使用原始 react-script 下的配置 **/ 42 | isUseOriginalConfig?: boolean; 43 | /** 是否使用 server 配置 **/ 44 | isUseServerConfig?: boolean; 45 | /** paths 脚本中webpack配置 使用的地址 */ 46 | paths?: Partial; 47 | 48 | /** 最终覆写 webpack 配置 **/ 49 | /** 客户端配置 */ 50 | overridesClientWebpack?: (conf: webpack.Configuration, env: "development" | "production", options: Options) => webpack.Configuration, 51 | /** 服务端配置 */ 52 | overridesServerWebpack?: (conf: webpack.Configuration, env: "development" | "production", options: Options) => webpack.Configuration; 53 | /** 公共覆盖配置 */ 54 | overridesCommonWebpack?: (conf: webpack.Configuration, env: "development" | "production", options: Options) => webpack.Configuration; 55 | // 最终的配置 56 | overridesWebpack?: WebpackConfigFunction 57 | 58 | /** 服务端打包入口 */ 59 | server_path?: string, 60 | /** 客户端打包入口 */ 61 | client_path?: string, 62 | /** 输出文件地址 */ 63 | output_path?: string; 64 | /** watch 配置 */ 65 | watchOptions?: webpack.Configuration["watchOptions"]; 66 | /** 代理 */ 67 | proxySetup?: (app: Application) => { 68 | path: string | string[], 69 | options?: MockerOption 70 | } 71 | } 72 | 73 | export interface OptionsProps { 74 | /** client 使用 NodeExternals **/ 75 | clientNodeExternals: boolean; 76 | /** server 使用 NodeExternals **/ 77 | serverNodeExternals: boolean; 78 | /** server 进行代码拆分 **/ 79 | serverIsChunk: boolean; 80 | /** client 进行代码拆分 **/ 81 | clientIsChunk: boolean; 82 | /** 是否使用原始 react-script 下的配置 **/ 83 | original: boolean, 84 | /** 是否压缩代码 mini < miniServer/miniClient **/ 85 | mini: boolean 86 | /** server 是否压缩代码 **/ 87 | miniServer: boolean 88 | /** client 是否压缩代码 **/ 89 | miniClient: boolean, 90 | target: "client" | "server" | "all" 91 | } -------------------------------------------------------------------------------- /core/src/overrides/env.ts: -------------------------------------------------------------------------------- 1 | // 重写环境变量 2 | import { OverridesProps } from "./../interface" 3 | 4 | export const restENV = (overrides: OverridesProps) => { 5 | if (overrides.env) { 6 | Object.entries(overrides.env).forEach(([key, value]) => { 7 | process.env[key] = value; 8 | }) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/src/overrides/index.ts: -------------------------------------------------------------------------------- 1 | // 获取 根目录下 自己定义的配置 2 | import fs from 'fs'; 3 | import { resolveModule, resolveApp, } from "./pathUtils" 4 | import { restENV } from "./env" 5 | import { OverridesProps } from "./../interface" 6 | import paths from "./path" 7 | 8 | 9 | const tsOptions = { 10 | compilerOptions: { 11 | sourceMap: false, 12 | target: 'es6', 13 | module: 'commonjs', 14 | moduleResolution: 'node', 15 | allowJs: false, 16 | declaration: true, 17 | strict: true, 18 | noUnusedLocals: true, 19 | experimentalDecorators: true, 20 | resolveJsonModule: true, 21 | esModuleInterop: true, 22 | removeComments: false, 23 | }, 24 | }; 25 | 26 | const confPath = resolveModule(resolveApp, '.kktssrrc'); 27 | 28 | let overrides: OverridesProps = { 29 | env: {}, 30 | // 服务端打包入口 31 | server_path: resolveModule(resolveApp, 'src/server'), 32 | // 客户端打包入口 33 | client_path: resolveModule(resolveApp, 'src/client'), 34 | /** 输出文件地址 */ 35 | output_path: resolveApp("dist"), 36 | /** 是否使用原始 react-script 下的配置, 📢注意:这个不控制 server 配置, **/ 37 | isUseOriginalConfig: false, 38 | /** 是否使用 server 配置 **/ 39 | isUseServerConfig: true, 40 | // paths 地址 41 | paths: {}, 42 | // 自定义 client 配置设置 43 | overridesClientWebpack: undefined, 44 | // 自定义 server 配置设置 45 | overridesServerWebpack: undefined, 46 | /** 公共覆盖配置 */ 47 | overridesCommonWebpack: undefined, 48 | // 最终自定义配置设置 49 | overridesWebpack: undefined, 50 | // 代理配置 51 | proxySetup: undefined 52 | }; 53 | 54 | export async function loaderConf(): Promise { 55 | let kktssrrc: OverridesProps = {}; 56 | 57 | try { 58 | if (fs.existsSync(confPath) && /.ts$/.test(confPath)) { 59 | require('ts-node').register(tsOptions); 60 | const config = await import(confPath); 61 | kktssrrc = config.default || kktssrrc 62 | } else if (fs.existsSync(confPath) && /.js$/.test(confPath)) { 63 | require('@babel/register')({ 64 | presets: [[require.resolve('babel-preset-react-app'), { runtime: 'classic', useESModules: false }]], 65 | }); 66 | const config = await import(confPath); 67 | kktssrrc = config.default || kktssrrc 68 | } 69 | 70 | overrides = { 71 | ...overrides, 72 | ...kktssrrc, 73 | } 74 | 75 | // 重写环境变量 76 | restENV(overrides) 77 | 78 | // 重写 paths 值 79 | const path = paths(overrides) 80 | overrides.paths = path 81 | return overrides; 82 | } catch (error) { 83 | const message = error && error.message ? error.message : ''; 84 | console.log('Invalid \x1b[31;1m .kktssrrc.js \x1b[0m file.\n', error); 85 | new Error(`Invalid .kktssrrc.js file. \n ${message}`); 86 | process.exit(1); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /core/src/overrides/overridesDevServer.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { devServerConfigPath, reactDevUtils } from "./pathUtils" 4 | import { OverridesProps } from "./../interface" 5 | import webpackDevServer from "webpack-dev-server" 6 | import MockerApi from "mocker-api" 7 | 8 | const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware'); 9 | const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); 10 | const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware'); 11 | 12 | export default async (overrides: OverridesProps, original: boolean) => { 13 | 14 | if (!original) { 15 | 16 | const openBrowserPath = `${reactDevUtils}/openBrowser`; 17 | 18 | require(openBrowserPath); 19 | 20 | // override config in memory 21 | require.cache[require.resolve(openBrowserPath)].exports = () => { }; 22 | } 23 | 24 | 25 | const webpackdDevServerConfigPath = require(`${devServerConfigPath}`) 26 | 27 | require.cache[require.resolve(devServerConfigPath)].exports = (proxy: webpackDevServer.Configuration['proxy'], allowedHost: string) => { 28 | 29 | const webpackdDevServerConfig = webpackdDevServerConfigPath(proxy, allowedHost) 30 | 31 | delete webpackdDevServerConfig.onAfterSetupMiddleware; 32 | delete webpackdDevServerConfig.onBeforeSetupMiddleware; 33 | 34 | webpackdDevServerConfig.setupMiddlewares = (middlewares: webpackDevServer.Middleware[], devServer: webpackDevServer) => { 35 | 36 | // Keep `evalSourceMapMiddleware` 37 | // middlewares before `redirectServedPath` otherwise will not have any effect 38 | // This lets us fetch source contents from webpack for the error overlay 39 | devServer.app.use(evalSourceMapMiddleware(devServer)); 40 | 41 | if (overrides.proxySetup && typeof overrides.proxySetup === "function") { 42 | // This registers user provided middleware for proxy reasons 43 | const result = overrides.proxySetup(devServer.app); 44 | if (result.path) { 45 | MockerApi(devServer.app, result.path, result.options) 46 | } 47 | } 48 | 49 | // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match 50 | devServer.app.use(redirectServedPath(overrides.paths.publicUrlOrPath)); 51 | 52 | // This service worker file is effectively a 'no-op' that will reset any 53 | // previous service worker registered for the same host:port combination. 54 | // We do this in development to avoid hitting the production cache if 55 | // it used the same host and port. 56 | // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432 57 | devServer.app.use(noopServiceWorkerMiddleware(overrides.paths.publicUrlOrPath)); 58 | 59 | return middlewares 60 | } 61 | 62 | return webpackdDevServerConfig 63 | 64 | } 65 | 66 | 67 | 68 | } -------------------------------------------------------------------------------- /core/src/overrides/overridesWebpackDevServerUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 临时 解决 ts 报错问题 3 | * **/ 4 | 5 | import { reactDevUtils } from "./pathUtils" 6 | import webpack from "webpack" 7 | import chalk from 'chalk'; 8 | 9 | const WebpackDevServerUtilsPath = `${reactDevUtils}/WebpackDevServerUtils` 10 | const WebpackDevServerUtils = require(`${WebpackDevServerUtilsPath}`) 11 | const clearConsole = require(`${reactDevUtils}/clearConsole`) 12 | const formatWebpackMessages = require(`${reactDevUtils}/formatWebpackMessages`) 13 | 14 | export interface Urls { 15 | lanUrlForConfig?: string | undefined; 16 | lanUrlForTerminal?: string | undefined; 17 | localUrlForTerminal: string; 18 | localUrlForBrowser: string; 19 | } 20 | 21 | function printInstructions(appName: string, urls: Urls, useYarn: boolean) { 22 | console.log(); 23 | console.log(`You can now view ${chalk.bold(appName)} in the browser.`); 24 | console.log(); 25 | 26 | if (urls.lanUrlForTerminal) { 27 | console.log( 28 | ` ${chalk.bold('Local:')} ${urls.localUrlForTerminal}` 29 | ); 30 | console.log( 31 | ` ${chalk.bold('On Your Network:')} ${urls.lanUrlForTerminal}` 32 | ); 33 | } else { 34 | console.log(` ${urls.localUrlForTerminal}`); 35 | } 36 | 37 | console.log(); 38 | console.log('Note that the development build is not optimized.'); 39 | console.log( 40 | `To create a production build, use ` + 41 | `${chalk.cyan(`${useYarn ? 'yarn' : 'npm run'} build`)}.` 42 | ); 43 | console.log(); 44 | } 45 | 46 | type CreateCompilerType = { 47 | appName: string; 48 | config: any; 49 | urls: Urls; 50 | useYarn: boolean; 51 | useTypeScript: boolean; 52 | webpack: any; 53 | } 54 | 55 | export default async () => { 56 | const isInteractive = process.stdout.isTTY; 57 | 58 | const createCompiler = ({ 59 | appName, 60 | config, 61 | urls, 62 | useYarn, 63 | useTypeScript, 64 | webpack, 65 | }: CreateCompilerType) => { 66 | // "Compiler" is a low-level interface to webpack. 67 | // It lets us listen to some events and provide our own custom messages. 68 | let compiler; 69 | try { 70 | compiler = webpack(config); 71 | } catch (err) { 72 | console.log(chalk.red('Failed to compile.')); 73 | console.log(); 74 | console.log(err.message || err); 75 | console.log(); 76 | process.exit(1); 77 | } 78 | // "invalid" event fires when you have changed a file, and webpack is 79 | // recompiling a bundle. WebpackDevServer takes care to pause serving the 80 | // bundle, so if you refresh, it'll wait instead of serving the old one. 81 | // "invalid" is short for "bundle invalidated", it doesn't imply any errors. 82 | compiler.hooks.invalid.tap('invalid', () => { 83 | if (isInteractive) { 84 | clearConsole(); 85 | } 86 | console.log('Compiling...'); 87 | }); 88 | 89 | let isFirstCompile = true; 90 | let tsMessagesPromise: any; 91 | //----------------------------- 注释 为了解决报错问题 -------------------------------- 92 | // if (useTypeScript) { 93 | // forkTsCheckerWebpackPlugin 94 | // .getCompilerHooks(compiler) 95 | // .waiting.tap('awaitingTypeScriptCheck', () => { 96 | // console.log( 97 | // chalk.yellow( 98 | // 'Files successfully emitted, waiting for typecheck results...' 99 | // ) 100 | // ); 101 | // }); 102 | // } 103 | 104 | // "done" event fires when webpack has finished recompiling the bundle. 105 | // Whether or not you have warnings or errors, you will get this event. 106 | compiler.hooks.done.tap('done', async (stats: webpack.Stats) => { 107 | if (isInteractive) { 108 | clearConsole(); 109 | } 110 | 111 | // We have switched off the default webpack output in WebpackDevServer 112 | // options so we are going to "massage" the warnings and errors and present 113 | // them in a readable focused way. 114 | // We only construct the warnings and errors for speed: 115 | // https://github.com/facebook/create-react-app/issues/4492#issuecomment-421959548 116 | const statsData = stats.toJson({ 117 | all: false, 118 | warnings: true, 119 | errors: true, 120 | }); 121 | 122 | const messages = formatWebpackMessages(statsData); 123 | const isSuccessful = !messages.errors.length && !messages.warnings.length; 124 | if (isSuccessful) { 125 | console.log(chalk.green('Compiled successfully!')); 126 | } 127 | if (isSuccessful && (isInteractive || isFirstCompile)) { 128 | printInstructions(appName, urls, useYarn); 129 | } 130 | isFirstCompile = false; 131 | 132 | // If errors exist, only show errors. 133 | if (messages.errors.length) { 134 | // Only keep the first error. Others are often indicative 135 | // of the same problem, but confuse the reader with noise. 136 | if (messages.errors.length > 1) { 137 | messages.errors.length = 1; 138 | } 139 | console.log(chalk.red('Failed to compile.\n')); 140 | console.log(messages.errors.join('\n\n')); 141 | return; 142 | } 143 | 144 | // Show warnings if no errors were found. 145 | if (messages.warnings.length) { 146 | console.log(chalk.yellow('Compiled with warnings.\n')); 147 | console.log(messages.warnings.join('\n\n')); 148 | 149 | // Teach some ESLint tricks. 150 | console.log( 151 | '\nSearch for the ' + 152 | chalk.underline(chalk.yellow('keywords')) + 153 | ' to learn more about each warning.' 154 | ); 155 | console.log( 156 | 'To ignore, add ' + 157 | chalk.cyan('// eslint-disable-next-line') + 158 | ' to the line before.\n' 159 | ); 160 | } 161 | }); 162 | 163 | // You can safely remove this after ejecting. 164 | // We only use this block for testing of Create React App itself: 165 | const isSmokeTest = process.argv.some( 166 | arg => arg.indexOf('--smoke-test') > -1 167 | ); 168 | if (isSmokeTest) { 169 | compiler.hooks.failed.tap('smokeTest', async () => { 170 | await tsMessagesPromise; 171 | process.exit(1); 172 | }); 173 | compiler.hooks.done.tap('smokeTest', async (stats: webpack.Stats) => { 174 | await tsMessagesPromise; 175 | if (stats.hasErrors() || stats.hasWarnings()) { 176 | process.exit(1); 177 | } else { 178 | process.exit(0); 179 | } 180 | }); 181 | } 182 | return compiler; 183 | } 184 | 185 | require.cache[require.resolve(WebpackDevServerUtilsPath)].exports = { 186 | ...WebpackDevServerUtils, 187 | createCompiler 188 | } 189 | 190 | } 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /core/src/overrides/path.ts: -------------------------------------------------------------------------------- 1 | import { reactScripts } from "./pathUtils" 2 | import { Paths, OverridesProps } from "../interface" 3 | import fs from 'fs'; 4 | 5 | /** 覆盖 */ 6 | export default (overrides: OverridesProps): Paths => { 7 | const pathsConfPath = `${reactScripts}/config/paths`; 8 | const pathsConf = require(pathsConfPath); 9 | const newPaths = { 10 | ...pathsConf, 11 | ...overrides.paths, 12 | appBuild: overrides.output_path 13 | } 14 | if (!fs.existsSync(newPaths.appIndexJs) && fs.existsSync(overrides.client_path)) { 15 | newPaths.appIndexJs = overrides.client_path 16 | } 17 | require.cache[require.resolve(`${reactScripts}/config/paths`)].exports = newPaths 18 | return newPaths 19 | } -------------------------------------------------------------------------------- /core/src/overrides/pathUtils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import { Paths } from "./../interface" 4 | 5 | export const appDirectory = fs.realpathSync(process.cwd()); 6 | 7 | export const resolveApp = (relativePath: string) => path.resolve(appDirectory, relativePath); 8 | 9 | export const reactScripts = path.join(require.resolve('react-scripts/package.json'), '..'); 10 | 11 | export const paths: Paths = require(`${reactScripts}/config/paths`); 12 | 13 | export const moduleFileExtensions: string[] = paths.moduleFileExtensions || []; 14 | 15 | export const devServerConfigPath = `${reactScripts}/config/webpackDevServer.config` 16 | 17 | export const webpackConfigPath = `${reactScripts}/config/webpack.config` 18 | 19 | export const reactDevUtils = path.join(require.resolve('react-dev-utils/package.json'), '..'); 20 | 21 | // Resolve file paths in the same order as webpack 22 | export const resolveModule = (resolveFn: { (relativePath: string): string; (arg0: string): fs.PathLike; }, filePath: string) => { 23 | const extension = moduleFileExtensions.find(extension => 24 | fs.existsSync(resolveFn(`${filePath}.${extension}`)) 25 | ); 26 | 27 | if (extension) { 28 | return resolveFn(`${filePath}.${extension}`); 29 | } 30 | 31 | return resolveFn(`${filePath}.js`); 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /core/src/script/build.ts: -------------------------------------------------------------------------------- 1 | 2 | import createCompiler from "../utils/getWebpackConfig" 3 | import fs from "fs-extra"; 4 | import FileSizeReporter from "react-dev-utils/FileSizeReporter"; 5 | import { webpackConfigPath } from "./../overrides/pathUtils" 6 | import { OptionsProps, Paths, } from "../interface" 7 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 8 | 9 | const chalk = require('react-dev-utils/chalk'); 10 | 11 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 12 | 13 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 14 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 15 | const isInteractive = process.stdout.isTTY; 16 | 17 | export interface OpaqueFileSizes { 18 | root: string; 19 | sizes: Record; 20 | } 21 | 22 | const build = async (options: OptionsProps) => { 23 | // 修复 运行 start 停止之后,再次运行 watch 报错 24 | delete require.cache[require.resolve(webpackConfigPath)]; 25 | 26 | const { compiler, overrides } = await createCompiler("production", options) 27 | 28 | checkBrowsers(overrides.paths.appPath, isInteractive).then(() => { 29 | console.log('Creating an optimized production build...'); 30 | 31 | fs.emptyDirSync(overrides.paths.appBuild); 32 | 33 | copyPublicFolder(overrides.paths); 34 | 35 | compiler.run((error, stats: any) => { 36 | if (!error) { 37 | if (stats.hasErrors()) { 38 | console.log('\x1b[31;1m KKT-SSR:BUILD:ERROR: \x1b[0m\n', stats.toString()); 39 | process.exit(1); 40 | } 41 | 42 | console.log(chalk.green('Compiled successfully.\n')); 43 | console.log('File sizes after gzip:\n'); 44 | printFileSizesAfterBuild( 45 | stats, 46 | { root: overrides.paths.appBuild, sizes: {} }, 47 | overrides.paths.appBuild, 48 | WARN_AFTER_BUNDLE_GZIP_SIZE, 49 | WARN_AFTER_CHUNK_GZIP_SIZE 50 | ); 51 | console.log(); 52 | 53 | } else { 54 | const message = error && error.message ? error.message : ''; 55 | console.log('\x1b[31;1m KKT-SSR:BUILD:ERROR: \x1b[0m\n', error); 56 | new Error(`KKT:BUILD:ERROR: \n ${message}`); 57 | process.exit(1); 58 | } 59 | }); 60 | }).catch((err: any) => { 61 | if (err && err.message) { 62 | console.log(err.message); 63 | } 64 | process.exit(1); 65 | }); 66 | } 67 | 68 | function copyPublicFolder(paths: Partial) { 69 | fs.copySync(paths.appPublic, paths.appBuild, { 70 | dereference: true, 71 | filter: (file: string) => file !== paths.appHtml, 72 | }); 73 | } 74 | 75 | export default build -------------------------------------------------------------------------------- /core/src/script/start.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import createCompiler from "../utils/getWebpackConfig" 4 | import { OptionsProps } from "../interface" 5 | import { webpackConfigPath, reactScripts, reactDevUtils } from "./../overrides/pathUtils" 6 | import overridesDevServer from "../overrides/overridesDevServer" 7 | import overridesWebpackDevServerUtils from "./../overrides/overridesWebpackDevServerUtils" 8 | export default async (options: OptionsProps) => { 9 | // 修复 运行 start 停止之后,再次运行 watch 报错 10 | delete require.cache[require.resolve(webpackConfigPath)]; 11 | delete require.cache[require.resolve(`${reactDevUtils}/openBrowser`)]; 12 | 13 | try { 14 | const { overrides, config } = await createCompiler("development", options) 15 | 16 | require.cache[require.resolve(webpackConfigPath)].exports = (env: string) => config; 17 | 18 | await overridesDevServer(overrides, options.original) 19 | 20 | await overridesWebpackDevServerUtils() 21 | 22 | require(`${reactScripts}/scripts/start`); 23 | 24 | } 25 | catch (error) { 26 | const message = error && error.message ? error.message : ''; 27 | console.log('\x1b[31;1m KKT-SSR:START:ERROR: \x1b[0m\n', error); 28 | new Error(`KKT-SSR:START:ERROR: \n ${message}`); 29 | process.exit(1); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /core/src/utils/ProcessingConfig.ts: -------------------------------------------------------------------------------- 1 | // 重置 create-react-app 中的 react-script配置 2 | import webpackNodeExternals from "webpack-node-externals" 3 | import webpack from "webpack" 4 | import { OptionsProps, OverridesProps } from "../interface" 5 | import CreateTemporaryAsset from "./plugins/CreateTemporaryAsset" 6 | import { restOutPut } from "./output" 7 | import { getWbpackBarPlugins, restWebpackManifestPlugin, clearHtmlTemp, addServerPlugins, AddDefinePlugin } from "./plugins" 8 | 9 | export type ProcessingConfigProps = ( 10 | newConfig: webpack.Configuration, 11 | type: "server" | "client", 12 | overrides: OverridesProps, 13 | nodeExternals: boolean, 14 | split: boolean, 15 | options: OptionsProps 16 | ) => webpack.Configuration 17 | 18 | 19 | const processingConfig: ProcessingConfigProps = (newConfig, type, overrides, nodeExternals, split, options) => { 20 | const isDev = process.env.NODE_ENV === "development" 21 | /** 入口 */ 22 | newConfig.entry = overrides[`${type}_path`] 23 | /** 加载 进度条 plugin */ 24 | newConfig = getWbpackBarPlugins(newConfig, { 25 | name: type, 26 | }) 27 | /** 输出配置 */ 28 | const out: webpack.Configuration["output"] = { 29 | filename: `${type}.js`, 30 | path: overrides.output_path, 31 | } 32 | if (type === "server") { 33 | /** 输出 类型 */ 34 | out.library = { type: "commonjs2" } 35 | } 36 | 37 | const HOST = process.env.HOST || 'localhost' 38 | const PORT = process.env.PORT || 3000 39 | 40 | const httpPath = `http://${HOST}:${PORT}` 41 | 42 | /** start 命令时候 配置前缀 为 devServer 端口 */ 43 | if (isDev) { 44 | out.publicPath = `${httpPath}/` 45 | } else { 46 | out.publicPath = `/` 47 | } 48 | 49 | /** 重置 输入配置 */ 50 | newConfig = restOutPut(newConfig, out) 51 | 52 | /** start 命令下 生成 client 端 asset 文件 **/ 53 | let isCreateAsset = false; 54 | 55 | if (type === "client" && isDev) { 56 | isCreateAsset = true 57 | } 58 | /** start 命令下 生成 server.js文件和 自动启动 server.js 服务 */ 59 | if (type === "server" && isDev) { 60 | newConfig = addServerPlugins(newConfig, out) 61 | } 62 | /** 重置 asset-manifest.json 文件内容 */ 63 | newConfig = restWebpackManifestPlugin(newConfig, overrides.paths, type, isCreateAsset, httpPath) 64 | /** 清除 html 模板方面的 plugin **/ 65 | newConfig = clearHtmlTemp(newConfig) 66 | 67 | newConfig.module.exprContextCritical = false; 68 | // 添加 define plugin 69 | newConfig = AddDefinePlugin(newConfig, overrides, isDev) 70 | 71 | newConfig.plugins.push( 72 | new CreateTemporaryAsset(`${overrides.output_path}/asset-${type}-manifest.json`) 73 | ) 74 | 75 | if (!split) { 76 | // 代码是否进行分割 77 | newConfig.plugins.push(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 })) 78 | } 79 | 80 | if (nodeExternals) { 81 | /** 82 | * https://www.npmjs.com/package/webpack-node-externals 83 | * 这个库扫描node_modules文件夹中的所有 node_modules 名称,并构建一个外部函数,告诉 Webpack 不要捆绑这些模块或它们的任何子模块 84 | * */ 85 | newConfig.externals = [webpackNodeExternals()] 86 | } 87 | 88 | if (!options.miniServer && type === "server") { 89 | /** server 端 去除代码压缩 */ 90 | newConfig.optimization.minimize = false 91 | newConfig.optimization.minimizer = [] 92 | } 93 | 94 | if (!options.miniClient && type === "client") { 95 | /** client 端 去除代码压缩 */ 96 | newConfig.optimization.minimize = false 97 | newConfig.optimization.minimizer = [] 98 | } 99 | 100 | return newConfig 101 | } 102 | 103 | export default processingConfig -------------------------------------------------------------------------------- /core/src/utils/getWebpackConfig.ts: -------------------------------------------------------------------------------- 1 | // 引入环境变量 2 | require(`${reactScripts}/config/env`); 3 | 4 | // 重置 create-react-app 中的 react-script配置 5 | import { reactScripts, webpackConfigPath } from "../overrides/pathUtils" 6 | import webpack from "webpack" 7 | import fs from 'fs'; 8 | import { OptionsProps, Options } from "../interface" 9 | import { loaderConf } from "../overrides" 10 | import { restDevModuleRuleCss, removeSourceMapLoader } from "./module" 11 | import processingConfig from "./ProcessingConfig" 12 | 13 | const { choosePort } = require('react-dev-utils/WebpackDevServerUtils'); 14 | 15 | // Tools like Cloud9 rely on this. 16 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 17 | const HOST = process.env.HOST || 'localhost'; 18 | 19 | export default async (env: "development" | "production", options: OptionsProps,) => { 20 | 21 | const isDev = env === "development" 22 | 23 | /** 端口处理 */ 24 | let PORT; 25 | if (isDev) { 26 | PORT = await choosePort(HOST, DEFAULT_PORT) 27 | } 28 | 29 | /** 加载自定义配置 */ 30 | const overrides = await loaderConf() 31 | 32 | process.env.PORT = PORT || "3000" 33 | process.env.HOST = HOST || "localhost"; 34 | /** 是否使用原始 react-script 下的配置, 📢注意:这个不控制 server 配置, **/ 35 | const original = options.original || overrides.isUseOriginalConfig 36 | 37 | const { overridesClientWebpack, overridesServerWebpack, overridesWebpack, overridesCommonWebpack, ...rest } = overrides 38 | 39 | const configFactory = require(`${webpackConfigPath}`); 40 | 41 | let configArr: webpack.Configuration[] = [] 42 | 43 | /**------------------------ client 配置 --------------------- */ 44 | if (fs.existsSync(overrides.client_path) && ["all", "client"].includes(options.target)) { 45 | const configClient = configFactory(env); 46 | 47 | let newConfigClient = configClient 48 | 49 | // 控制 client 是否使用 ssr,默认情况下使用 50 | if (!original) { 51 | 52 | newConfigClient = processingConfig(configClient, "client", overrides, options.clientNodeExternals, options.clientIsChunk, options) 53 | } 54 | if (!original) { 55 | // 去除 source-map-loader 56 | newConfigClient = removeSourceMapLoader(newConfigClient) 57 | } 58 | if (overridesCommonWebpack) { 59 | newConfigClient = overridesCommonWebpack(newConfigClient, env, { ...rest, ...options } as Options) 60 | } 61 | if (overridesClientWebpack) { 62 | newConfigClient = overridesClientWebpack(newConfigClient, env, { ...rest, ...options } as Options) 63 | } 64 | configArr.push(newConfigClient) 65 | } 66 | 67 | /**------------------------ server 配置 --------------------- */ 68 | if (fs.existsSync(overrides.server_path) && overrides.isUseServerConfig && ["all", "server"].includes(options.target)) { 69 | 70 | const configServer = configFactory(env); 71 | 72 | let newConfigServer = processingConfig(configServer, "server", overrides, options.serverNodeExternals, options.serverIsChunk, options) 73 | 74 | newConfigServer.devtool = false 75 | newConfigServer.target = "node14" 76 | 77 | /** server 处理 css */ 78 | newConfigServer = restDevModuleRuleCss(newConfigServer) 79 | /** 去除 source-map-loader */ 80 | newConfigServer = removeSourceMapLoader(newConfigServer) 81 | 82 | if (overridesCommonWebpack) { 83 | 84 | newConfigServer = overridesCommonWebpack(newConfigServer, env, { ...rest, ...options } as Options) 85 | 86 | } 87 | 88 | if (overridesServerWebpack) { 89 | 90 | newConfigServer = overridesServerWebpack(newConfigServer, env, { ...rest, ...options } as Options) 91 | 92 | } 93 | 94 | configArr.push(newConfigServer) 95 | } 96 | 97 | /**------------------------ other --------------------- */ 98 | if (overridesWebpack && typeof overridesWebpack === "function") { 99 | 100 | configArr = overridesWebpack(configArr, env, { ...rest, ...options } as Options) as webpack.Configuration[] 101 | 102 | } 103 | 104 | return { 105 | compiler: isDev ? undefined : webpack(configArr), 106 | config: configArr, 107 | overrides 108 | } 109 | } -------------------------------------------------------------------------------- /core/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./module" 2 | export * from "./plugins" 3 | export * from "./output" 4 | 5 | type BoolenValue = boolean | undefined | "false" | "true" 6 | 7 | export const getBoolean = (one: BoolenValue, two: BoolenValue, defaultValue: boolean = false) => { 8 | let value = defaultValue 9 | if (typeof one === "boolean") { 10 | value = one 11 | } else if (typeof two === "boolean") { 12 | value = two 13 | } 14 | 15 | if ((typeof one === "string" && one === "true") || (typeof two === "string" && two === "true")) { 16 | value = true 17 | } 18 | 19 | if ((typeof one === "string" && one === "false") || (typeof two === "string" && two === "false")) { 20 | value = false 21 | } 22 | 23 | return value 24 | } -------------------------------------------------------------------------------- /core/src/utils/module/index.ts: -------------------------------------------------------------------------------- 1 | import { WebpackConfiguration } from 'kkt'; 2 | import webpack from "webpack" 3 | 4 | const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); 5 | 6 | // style files regexes 7 | const cssRegex = /\.css$/; 8 | const cssModuleRegex = /\.module\.css$/; 9 | const sassRegex = /\.(scss|sass)$/; 10 | const sassModuleRegex = /\.module\.(scss|sass)$/; 11 | const lessModuleRegex = /\.module\.less$/; 12 | const lessRegex = /\.less$/; 13 | 14 | const getToString = (rule: RegExp) => { 15 | return rule.toString(); 16 | }; 17 | 18 | // loader source-map-loader 19 | export const removeSourceMapLoader = (conf: WebpackConfiguration): WebpackConfiguration => { 20 | return { 21 | ...conf, 22 | module: { 23 | ...conf.module, 24 | rules: conf.module.rules.filter((rule) => !(rule !== "..." && /source-map-loader/.test(rule.loader) && /\.(js|mjs|jsx|ts|tsx|css)$/.toString() === rule.test.toString())) 25 | } 26 | } 27 | } 28 | 29 | // node 环境 把 css 进行处理 30 | export const restDevModuleRuleCss = (conf: WebpackConfiguration,): WebpackConfiguration => { 31 | return { 32 | ...conf, 33 | plugins: conf.plugins.filter(plugin => plugin && plugin.constructor && plugin.constructor.name !== "MiniCssExtractPlugin"), 34 | module: { 35 | ...conf.module, 36 | rules: getModuleCSSRules(conf.module.rules,) 37 | }, 38 | } 39 | } 40 | 41 | /** 42 | * 1. 去除 style-loader|mini-css-extract-plugin 43 | * 2. 修改 css-loader 配置 44 | * */ 45 | export const getModuleCSSRules = (rules: (webpack.RuleSetRule | '...')[], shouldUseSourceMap: boolean = false) => { 46 | const newRules: any = []; 47 | 48 | const cssModuleOption = (mode: "icss" | "local") => ({ 49 | importLoaders: 3, 50 | sourceMap: shouldUseSourceMap, 51 | modules: { 52 | mode, 53 | getLocalIdent: getCSSModuleLocalIdent, 54 | exportOnlyLocals: true 55 | }, 56 | }); 57 | 58 | 59 | rules.forEach((rule) => { 60 | if (typeof rule === 'string') { 61 | newRules.push(rule); 62 | return; 63 | } 64 | 65 | if (/(style-loader|mini-css-extract-plugin)/.test(rule.loader)) { 66 | 67 | } else if (rule && typeof rule === 'object' && rule.test && /css-loader/.test(rule.loader) && !/postcss-loader/.test(rule.loader)) { 68 | const isModule = [getToString(cssModuleRegex), getToString(sassModuleRegex), getToString(lessModuleRegex),].includes(rule.test.toString()) 69 | newRules.push({ 70 | loader: require.resolve("css-loader"), 71 | options: cssModuleOption(isModule ? "local" : "icss") 72 | }) 73 | } else if (rule.oneOf) { 74 | const newOneOf = rule.oneOf.map((item) => { 75 | if ( 76 | item.test && 77 | [ 78 | getToString(cssRegex), 79 | getToString(cssModuleRegex), 80 | getToString(sassRegex), 81 | getToString(sassModuleRegex), 82 | getToString(lessModuleRegex), 83 | getToString(lessRegex), 84 | ].includes(item.test.toString()) 85 | ) { 86 | const isModule = [getToString(cssModuleRegex), getToString(sassModuleRegex), getToString(lessModuleRegex),].includes(item.test.toString()) 87 | let newUse; 88 | if (Array.isArray(item.use)) { 89 | newUse = item.use.map((ite) => { 90 | if (typeof ite === 'string' && /(style-loader|mini-css-extract-plugin)/.test(ite)) { 91 | return false 92 | } else if (typeof ite === 'string' && /css-loader/.test(ite) && !/postcss-loader/.test(ite)) { 93 | return { 94 | loader: require.resolve("css-loader"), 95 | options: cssModuleOption(isModule ? "local" : "icss") 96 | } 97 | } else if (typeof ite === 'object' && /(style-loader|mini-css-extract-plugin)/.test(ite.loader)) { 98 | return false 99 | } else if (typeof ite === 'object' && /css-loader/.test(ite.loader) && !/postcss-loader/.test(ite.loader)) { 100 | return { 101 | loader: require.resolve("css-loader"), 102 | options: cssModuleOption(isModule ? "local" : "icss") 103 | } 104 | } 105 | return ite; 106 | }).filter(Boolean); 107 | } 108 | return { 109 | ...item, 110 | use: newUse || [], 111 | }; 112 | } 113 | return item; 114 | }); 115 | newRules.push({ ...rule, oneOf: newOneOf }); 116 | } else { 117 | newRules.push(rule); 118 | } 119 | }); 120 | return newRules; 121 | }; 122 | 123 | -------------------------------------------------------------------------------- /core/src/utils/output/index.ts: -------------------------------------------------------------------------------- 1 | import { WebpackConfiguration } from 'kkt'; 2 | 3 | /** 输出配置 */ 4 | export const restOutPut = (conf: WebpackConfiguration, options: WebpackConfiguration['output']): WebpackConfiguration => { 5 | return { 6 | ...conf, 7 | output: { 8 | ...conf.output, 9 | ...options, 10 | }, 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /core/src/utils/plugins/CreateTemporaryAsset.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 解决 打包时 页面引入 asset 报错问题 3 | * **/ 4 | import FS from 'fs-extra'; 5 | export default class CreateTemporaryAsset { 6 | pathname = "" 7 | 8 | constructor(pathname: string) { 9 | this.pathname = pathname 10 | } 11 | 12 | apply() { 13 | if (!FS.existsSync(this.pathname)) { 14 | FS.ensureFileSync(this.pathname) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /core/src/utils/plugins/devServer.ts: -------------------------------------------------------------------------------- 1 | // 开发模式 下 生成 server.js 文件 2 | import webpack from "webpack" 3 | 4 | import FS from 'fs-extra'; 5 | 6 | 7 | class CreateTemporaryAsset { 8 | 9 | filename = "server.js" 10 | outputPath = "" 11 | 12 | constructor(props: { filename: string, outputPath: string, }) { 13 | this.filename = props.filename 14 | this.outputPath = props.outputPath 15 | } 16 | 17 | apply(compiler: webpack.Compiler) { 18 | compiler.hooks.thisCompilation.tap('DevServerPlugins', (childCompilation) => { 19 | childCompilation.hooks.processAssets.tap('DevServerPlugins', (compilationAssets) => { 20 | const serverFile = compilationAssets[this.filename] 21 | if (!FS.existsSync(this.outputPath)) { 22 | FS.ensureDirSync(this.outputPath) 23 | } 24 | FS.writeFileSync(`${this.outputPath}/${this.filename}`, serverFile.buffer(), { flag: "w+", encoding: "utf-8" }) 25 | }); 26 | }); 27 | } 28 | } 29 | 30 | export default CreateTemporaryAsset -------------------------------------------------------------------------------- /core/src/utils/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import webpack from "webpack" 2 | import DevServerPlugins from "./devServer" 3 | import { WebpackConfiguration } from 'kkt'; 4 | import nodemonWebpackPlugin from "nodemon-webpack-plugin" 5 | import { WebpackManifestPlugin, Manifest, } from 'webpack-manifest-plugin'; 6 | import { FileDescriptor } from 'webpack-manifest-plugin/dist/helpers'; 7 | import path from "path" 8 | import { Paths, OverridesProps } from "./../../interface" 9 | import FS from 'fs-extra'; 10 | import WebpackBar from 'webpackbar'; 11 | 12 | // plugin 根据 client server 13 | /** 加 进度条, */ 14 | export const getWbpackBarPlugins = (conf: WebpackConfiguration, opt: WebpackBar["options"]): WebpackConfiguration => { 15 | const options = { ...(opt || {}) }; 16 | if (options.name === 'server' && !options.color) { 17 | options.color = 'yellow'; 18 | } 19 | const plugins = (conf.plugins || []).concat([new WebpackBar({ ...options })]); 20 | return { 21 | ...conf, 22 | plugins, 23 | }; 24 | }; 25 | 26 | /** 清理 html 模板 plugin */ 27 | export const clearHtmlTemp = (conf: WebpackConfiguration): WebpackConfiguration => { 28 | const plugins = [] 29 | .concat(conf.plugins) 30 | .filter( 31 | plugin => 32 | plugin && 33 | !/(HtmlWebpackPlugin|InlineChunkHtmlPlugin|InterpolateHtmlPlugin)/.test( 34 | plugin.constructor.name 35 | ) 36 | ); 37 | return { ...conf, plugins }; 38 | }; 39 | 40 | export interface WebpackManifestPluginGenerateProps { 41 | seed: Record, 42 | files: FileDescriptor[], 43 | entrypoints: Record, 44 | isCreateAsset: boolean, 45 | httpPath: string 46 | paths: Partial, 47 | type?: string, 48 | } 49 | 50 | /** WebpackManifestPlugin 中 generate **/ 51 | export const WebpackManifestPluginGenerate = ({ seed, files, entrypoints, isCreateAsset, httpPath, paths, type }: WebpackManifestPluginGenerateProps): Manifest => { 52 | const getPahts = (name: string) => { 53 | if (!isCreateAsset || /^http/.test(name)) { 54 | if (/^\//.test(name) || /^http/.test(name)) { 55 | return name 56 | } 57 | return "/" + name 58 | } 59 | if (/^\//.test(name)) { 60 | return httpPath + name 61 | } 62 | return httpPath + "/" + name 63 | } 64 | const routhPaths: Record = {} 65 | const manifestFiles = files.reduce((manifest, file) => { 66 | manifest[file.name] = getPahts(file.path); 67 | if (!(file.name.endsWith('.map') || file.name.endsWith('.hot-update.js'))) { 68 | const routePath = `${file.name}`.replace(/.(css|js)$/, "") 69 | if (!routhPaths[routePath]) { 70 | routhPaths[routePath] = {} 71 | } 72 | const extname = path.extname(file.name).replace(".", "") as "css" | "js"; //获取文件的后缀名 73 | routhPaths[routePath][extname] = getPahts(file.path); 74 | } 75 | return manifest; 76 | }, seed); 77 | 78 | const clientOrServer: Record = { css: null, js: null } 79 | 80 | const entrypointFiles = entrypoints.main.filter( 81 | fileName => !(fileName.endsWith('.map') || fileName.endsWith('.hot-update.js')) 82 | ).map((fileName) => getPahts(fileName)); 83 | if (type) { 84 | entrypointFiles.forEach((filename) => { 85 | const extname = path.extname(filename).replace(".", "") as "css" | "js"; //获取文件的后缀名 86 | clientOrServer[extname] = getPahts(filename) 87 | }) 88 | } 89 | const result = { 90 | ...routhPaths, 91 | files: manifestFiles, 92 | entrypoints: entrypointFiles, 93 | [type]: clientOrServer, 94 | } 95 | if (!type) { 96 | delete result[type] 97 | } 98 | if (isCreateAsset) { 99 | if (!FS.existsSync(paths.appBuild)) { 100 | FS.ensureDirSync(paths.appBuild) 101 | } 102 | const outPath = type ? `${paths.appBuild}/asset-${type}-manifest.json` : `${paths.appBuild}/asset-manifest.json` 103 | FS.writeFileSync(outPath, JSON.stringify(result, null, 2), { flag: "w+", encoding: "utf-8" }) 104 | } 105 | return { 106 | ...result, 107 | }; 108 | } 109 | 110 | /** 重置 WebpackManifestPlugin 输出名称 */ 111 | export const restWebpackManifestPlugin = ( 112 | conf: WebpackConfiguration, 113 | paths: Partial, 114 | type?: string, 115 | isCreateAsset: boolean = false, 116 | httpPath: string = '' 117 | ): WebpackConfiguration => { 118 | const plugins = [] 119 | .concat(conf.plugins) 120 | .filter( 121 | plugin => plugin && plugin.constructor.name !== 'WebpackManifestPlugin' 122 | ) 123 | .concat([ 124 | new WebpackManifestPlugin({ 125 | fileName: type ? `asset-${type}-manifest.json` : 'asset-manifest.json', 126 | publicPath: paths.publicUrlOrPath, 127 | generate: (seed, files, entrypoints) => WebpackManifestPluginGenerate({ 128 | seed, 129 | files, 130 | entrypoints, 131 | isCreateAsset, 132 | httpPath, 133 | paths, 134 | type 135 | }), 136 | }), 137 | ]); 138 | return { ...conf, plugins }; 139 | }; 140 | 141 | export const addServerPlugins = (conf: WebpackConfiguration, out: webpack.Configuration["output"]) => { 142 | conf.plugins.push( 143 | /** 生成 server.js 文件 */ 144 | new DevServerPlugins({ 145 | filename: `${out.filename}`, 146 | outputPath: out.path 147 | }), 148 | /** 自动启动 server.js 文件服务 */ 149 | new nodemonWebpackPlugin({ 150 | script: `${out.path}/${out.filename}`, 151 | watch: [`${out.path}`] 152 | }), 153 | ) 154 | return conf 155 | } 156 | 157 | // 添加 DefinePlugin 158 | export const AddDefinePlugin = (conf: WebpackConfiguration, overrides: OverridesProps, isWebpackDevServer: boolean,) => { 159 | 160 | const HOST = process.env.HOST || 'localhost' 161 | const PORT = process.env.PORT || 3000 162 | 163 | const define = { 164 | /** 输出文件地址 */ 165 | OUTPUT_PUBLIC_PATH: JSON.stringify(overrides.output_path), 166 | /** server 端获取 asset-client-mainifest.json 文件的最终输出位置 */ 167 | KKT_PUBLIC_DIR: JSON.stringify(process.env.KKT_PUBLIC_DIR || overrides.output_path), 168 | /** 当前ip地址 **/ 169 | HOST: JSON.stringify(HOST), 170 | /** 当前端口 **/ 171 | PORT: JSON.stringify(PORT), 172 | /** 是否是 start 模式 **/ 173 | /** start 模式下 文件获取地址 */ 174 | HOSTAPI: JSON.stringify(undefined), 175 | "process.env.PORT": JSON.stringify(PORT), 176 | "process.env.HOSTAPI": JSON.stringify(process.env.HOSTAPI || undefined), 177 | "process.env.HOST": JSON.stringify(HOST) 178 | } 179 | 180 | if (isWebpackDevServer) { 181 | // 代理 服务的 ip 地址 182 | define.HOSTAPI = JSON.stringify(`http://${HOST}:${PORT}`) 183 | define["process.env.HOSTAPI"] = JSON.stringify(`http://${HOST}:${PORT}`) 184 | } 185 | 186 | conf.plugins.push( 187 | new webpack.DefinePlugin(define) 188 | ) 189 | return conf 190 | } -------------------------------------------------------------------------------- /core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "target": "es2017", 8 | "noImplicitAny": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node", 11 | "sourceMap": true, 12 | "outDir": "lib", 13 | "baseUrl": "./" 14 | }, 15 | "include": ["src"] 16 | } 17 | -------------------------------------------------------------------------------- /example/basic-plugins/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | package-lock.json 4 | coverage 5 | dist 6 | .DS_Store 7 | .cache 8 | .vscode 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | *.bak 15 | *.tem 16 | *.temp 17 | #.swp 18 | *.*~ 19 | ~*.* 20 | -------------------------------------------------------------------------------- /example/basic-plugins/.kktrc.ts: -------------------------------------------------------------------------------- 1 | 2 | import WebpackPluginSSRProps, { clearHtmlTemp, createNewWebpackManifestPlugin } from '@kkt/plugin-ssr'; 3 | import { WebpackConfiguration } from "kkt" 4 | export default (conf: WebpackConfiguration, env: "development" | "production") => { 5 | const paths = require("react-scripts/config/paths") 6 | conf.plugins?.push(new WebpackPluginSSRProps()); 7 | conf.plugins?.push(createNewWebpackManifestPlugin(paths, "client", env === "development")); 8 | conf = clearHtmlTemp(conf) 9 | if (conf.module) { 10 | conf.module.exprContextCritical = false; 11 | } 12 | return conf; 13 | }; 14 | -------------------------------------------------------------------------------- /example/basic-plugins/README.md: -------------------------------------------------------------------------------- 1 | Basic Example 2 | --- 3 | 4 | The [react](https://github.com/facebook/react) base application. 5 | 6 | ## Development 7 | 8 | Runs the project in development mode. 9 | 10 | ```bash 11 | npm run start 12 | ``` 13 | 14 | Runs Node Server 15 | 16 | ```bash 17 | npm run server 18 | ``` 19 | 20 | ## production 21 | 22 | Builds the app for production to the build folder. 23 | 24 | ```bash 25 | npm run build 26 | ``` 27 | 28 | The build is minified and the filenames include the hashes. 29 | Your app is ready to be deployed! 30 | -------------------------------------------------------------------------------- /example/basic-plugins/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/basic-plugins", 3 | "version": "3.1.13", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "server": "node build/server.js", 8 | "nodemon": "nodemon build/server.js", 9 | "build": "kkt build", 10 | "start": "kkt start" 11 | }, 12 | "keywords": [], 13 | "dependencies": { 14 | "express": "~4.16.4", 15 | "react": "~17.0.2", 16 | "react-dom": "~17.0.2" 17 | }, 18 | "devDependencies": { 19 | "@kkt/plugin-ssr": "3.1.13", 20 | "kkt": "~7.1.5" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/basic-plugins/public/favicon.ico: -------------------------------------------------------------------------------- 1 |  �F )  95@@ �n�PNG 2 |  3 | IHDR(-S�PLTE""""""""""""""""""2PX=r�)7;*:>H��-BGE��8do5Xb6[eK��K��1MU9gs3S\I��:gt'03@{�V��T��A}�V��@y�6\fH��-CII��E��+;@7_i7_jF��J��K��H��-BHa��,@FC��L��&.0W��N��I��$)+B��J��R��?v�>s�>u�S��=qP��P��P��,?D4U^%+-M��K��%+,2OX+!�g��Hg��Eߏܵ} ݻ����kd���Jo���3�L"J������Q�$��ļff�,�5i9̟�H�/mB��w��w;D �+&�W����D�o@ʴRI��B�om.۳�IEND�B`��PNG 4 |  5 | IHDRש��ePLTE""""""""""""""""""""""""2RZN��J��3R[J��)59Y��0KS4W`Q��L��%+-0JR)6::gtC��"##?v�U��?w�u�S��/HNM��_��\��M��8doD��D��>t�+=B[��,>C>t�u�;kxG��R��/HN&-/@y�>s�>t�@z�]��P��$'(D��]��.''�`C$F�� �(�x"6X�c�T��L�@I�;dd-�|�P,Ȕ9�R���f3��F�VmM �X�����@Y�7����N�=� ���ʪu }֬�+�e�aiq ��76����=h �Z���l��}�ʱ�[F�I9A�k9��� ��3��9Ρ�qB~��b���U_�^��[��w��{z�v�z��(��(��(����f�q��G���k���Y���f���~�:*4�Q\O>�����<ד�W���Z|ދ�7���jT���n�����`$H�+�GO���*�x����X*|�^�dIEND�B`��PNG 9 |  10 | IHDR@@����:PLTE"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""%+-@y�W��`��^��S��C��,>C*8u�K��`��A}�L��8do=r�%+,@y�^��S��)59=q�P��U��"#$P��\��0JQQ��"##U��#&&_��>t�>s�`��_��5Yc1OW5Zd1NV+=B1MU+;@/GM\��*;?3S\)8<2RZ_��+=A]��,@F,@E&-/0KS7alO��9dp8amB~�E��P��N��'023T]]��?x�3U^C��6\gU��&.0D��7_iR��H��I��M��$(*?v�Z��X��-AG#$%[��8co[��W��C��'25?v�8bn%*+L��N��2PX)7;=p~(58^��P��4WaQ��T��0JRQ��T��I��6]hR��T��0LSF��9eqE��E��9gsF��C��#&'\��`��&/16\fB��A{�R��]��(47%,.*:>*9=9fr:gt7^iU��?w�Z��X��^��Q��H��)6:V��'034U^E��.EL.FMK��@z�S�tRNS*������� ���+����Ԑ,����=V� IDATx�˵C!��C|���^yR]�M��O]����0N�2���"���(0V�(�Y%PDT-~(m��!�K �Y�~���I�f{����a����3��Op&�Ф��x��#�j��ڶm�m��c)]m����)Ƨgf�hk���Ҡ�gg�Ǚ���+X���uiyVת�k���\[�:,�6��jIJ ;�"�;��X���f�������S����8=�o�;���(���ӥBk�e��\7p+m����N�<��Q�O����y�g�tt��o������V����s������&_�a���V~��?�*8�Q ;8��,��� f���1 �x��ק�*���A���a#����#�nP�i+��C�,�����_�Nb��ø��H�B*�Ҧ �L(^<�Á�L6pJ�P������%"�R,�9�e3eR�a1�( 11 | ��q�8َ��mK˱mƶm���yi!�ΪY�u���_��?i���+���A|�{���?��_En).J�D�<� 12 | ���Z\Ts�R*( ��J���uX/ 4J9��5�DE�4k�4�&i�V4������vsf:�g,���BC��$�����@�I_?<�! ^���ӽ����B�%L�w�FD1���(F���H�%0����؄(�0���'���N.0u�@�Y�PW�I�aN�K ���?�ӵ�=�e�v/c����0c0�2��:�06R-u�ĭ\Q̶�䴼�6R# �F��6��rՁ��u��m�����I�i~ ����ÏsP�"� � eiy��P����򒧡���,S]U��V��֔��Z��o���xz���Snm�{ںwaل��Ż���(mg/��������[�b��q��&կ��$�zȊ�H>a�KT1/��1O��0�.h͇Y�A��� 13 | -�>ۋ���Xբ�}ߨ� ��;��N��v���θ�1����O@&v/��_��\��\�.��+0�;!f� ���%� JY�O�Ž'/�]_�;��'"&N�n aQ�^�cx�A��IEND�B`� -------------------------------------------------------------------------------- /example/basic-plugins/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | KKT 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example/basic-plugins/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /example/basic-plugins/src/app/App.module.css: -------------------------------------------------------------------------------- 1 | 2 | .warpper { 3 | border: 1px solid red; 4 | padding-left: 220px; 5 | } -------------------------------------------------------------------------------- /example/basic-plugins/src/app/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | describe('', () => { 6 | test('renders without exploding', () => { 7 | ReactDOM.render(, document.createElement('div')); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /example/basic-plugins/src/app/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // @ts-ignore 3 | import styles from './App.module.css'; 4 | import './app.css'; 5 | 6 | const App = () => ( 7 |
8 | Welcome to KKT. 9 |
Btn
10 |
11 | ); 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /example/basic-plugins/src/app/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 100px; 3 | font-family: sans-serif; 4 | } 5 | 6 | .btn { 7 | color: red; 8 | } -------------------------------------------------------------------------------- /example/basic-plugins/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './app/App'; 4 | 5 | ReactDOM.hydrate(, document.getElementById('root')); 6 | 7 | // @ts-ignore 8 | if (module.hot) { 9 | // @ts-ignore 10 | module.hot.accept(); 11 | } 12 | -------------------------------------------------------------------------------- /example/basic-plugins/src/server.ts: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import app from './serverIndex'; 3 | 4 | const logs = console.log; // eslint-disable-line 5 | 6 | const server = http.createServer(app); 7 | let currentApp = app; 8 | const PORT = parseInt(process.env.PORT || 3000) + 1; 9 | const HOST = process.env.HOST || 'localhost'; 10 | 11 | server.listen(PORT, (error) => { 12 | if (error) { 13 | logs(error); 14 | } 15 | logs('🚀 started!', `PORT: http://${HOST}:${PORT}`); 16 | }); 17 | 18 | if (module.hot) { 19 | logs('✅ Server-side HMR Enabled!'); 20 | module.hot.accept('./serverIndex', () => { 21 | logs('🔁 HMR Reloading `./serverIndex`...'); 22 | server.removeListener('request', currentApp); 23 | const newApp = require('./serverIndex').default; // eslint-disable-line 24 | server.on('request', newApp); 25 | currentApp = newApp; 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /example/basic-plugins/src/serverIndex.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import express from 'express'; 3 | import { renderToString } from 'react-dom/server'; 4 | import App from './app/App'; 5 | 6 | import Path from 'path'; 7 | import FS from 'fs'; 8 | // @ts-ignore 9 | const assetsMainifest = new Function(`return ${FS.readFileSync(`${OUTPUT_PUBLIC_PATH}/asset-client-manifest.json`, "utf-8")}`)() 10 | 11 | const appDirectory = FS.realpathSync(process.cwd()); 12 | const resolveApp = (relativePath) => Path.resolve(appDirectory, relativePath); 13 | const server = express(); 14 | 15 | server 16 | .disable('x-powered-by') 17 | .use(express.static(resolveApp('http://localhost:3000'))) 18 | .get('/*', (req, res) => { 19 | const markup = renderToString(); 20 | res.status(200).send( 21 | ` 22 | 23 | 24 | 25 | 26 | 27 | Welcome to KKT 28 | 29 | ${assetsMainifest.client.css ? `` : ''} 30 | 31 | 32 |
${markup}
33 | ${assetsMainifest.client.js ? `` : ""} 34 | 35 | `, 36 | ); 37 | }); 38 | 39 | export default server; 40 | -------------------------------------------------------------------------------- /example/basic-plugins/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "esnext", 6 | "dom" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "declaration": true, 19 | "baseUrl": ".", 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true, 22 | "noEmit": true, 23 | "paths": { 24 | "@/*": [ 25 | "./src/*" 26 | ], 27 | "@@/*": [ 28 | "./src/.uiw/*" 29 | ] 30 | } 31 | }, 32 | "include": [ 33 | "src/**/*", 34 | ".kktrc.ts" 35 | ] 36 | } -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | package-lock.json 4 | coverage 5 | dist 6 | .DS_Store 7 | .cache 8 | .vscode 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | *.bak 15 | *.tem 16 | *.temp 17 | #.swp 18 | *.*~ 19 | ~*.* 20 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/README.md: -------------------------------------------------------------------------------- 1 | Basic Example 2 | --- 3 | 4 | The [react](https://github.com/facebook/react) base application. 5 | 6 | ## Development 7 | 8 | Runs the project in development mode. 9 | 10 | ```bash 11 | npm run start 12 | ``` 13 | 14 | Runs Node Server 15 | 16 | ```bash 17 | npm run server 18 | ``` 19 | 20 | ## production 21 | 22 | Builds the app for production to the build folder. 23 | 24 | ```bash 25 | npm run build 26 | ``` 27 | 28 | The build is minified and the filenames include the hashes. 29 | Your app is ready to be deployed! 30 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/basic-routes-rematch-new", 3 | "version": "3.1.13", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "server": "node dist/server.js", 8 | "build": "kkt-ssr build", 9 | "start": "kkt-ssr start" 10 | }, 11 | "keywords": [], 12 | "dependencies": { 13 | "@rematch/core": "2.2.0", 14 | "@rematch/loading": "2.1.2", 15 | "express": "~4.16.4", 16 | "react": "~17.0.2", 17 | "react-dom": "~17.0.2", 18 | "react-redux": "^7.2.6", 19 | "react-router": "^6.2.2", 20 | "react-router-dom": "^6.2.2", 21 | "serialize-javascript": "~6.0.0" 22 | }, 23 | "devDependencies": { 24 | "@kkt/ssr": "3.1.13", 25 | "kkt": "~7.1.5" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/public/favicon.ico: -------------------------------------------------------------------------------- 1 |  �F )  95@@ �n�PNG 2 |  3 | IHDR(-S�PLTE""""""""""""""""""2PX=r�)7;*:>H��-BGE��8do5Xb6[eK��K��1MU9gs3S\I��:gt'03@{�V��T��A}�V��@y�6\fH��-CII��E��+;@7_i7_jF��J��K��H��-BHa��,@FC��L��&.0W��N��I��$)+B��J��R��?v�>s�>u�S��=qP��P��P��,?D4U^%+-M��K��%+,2OX+!�g��Hg��Eߏܵ} ݻ����kd���Jo���3�L"J������Q�$��ļff�,�5i9̟�H�/mB��w��w;D �+&�W����D�o@ʴRI��B�om.۳�IEND�B`��PNG 4 |  5 | IHDRש��ePLTE""""""""""""""""""""""""2RZN��J��3R[J��)59Y��0KS4W`Q��L��%+-0JR)6::gtC��"##?v�U��?w�u�S��/HNM��_��\��M��8doD��D��>t�+=B[��,>C>t�u�;kxG��R��/HN&-/@y�>s�>t�@z�]��P��$'(D��]��.''�`C$F�� �(�x"6X�c�T��L�@I�;dd-�|�P,Ȕ9�R���f3��F�VmM �X�����@Y�7����N�=� ���ʪu }֬�+�e�aiq ��76����=h �Z���l��}�ʱ�[F�I9A�k9��� ��3��9Ρ�qB~��b���U_�^��[��w��{z�v�z��(��(��(����f�q��G���k���Y���f���~�:*4�Q\O>�����<ד�W���Z|ދ�7���jT���n�����`$H�+�GO���*�x����X*|�^�dIEND�B`��PNG 9 |  10 | IHDR@@����:PLTE"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""%+-@y�W��`��^��S��C��,>C*8u�K��`��A}�L��8do=r�%+,@y�^��S��)59=q�P��U��"#$P��\��0JQQ��"##U��#&&_��>t�>s�`��_��5Yc1OW5Zd1NV+=B1MU+;@/GM\��*;?3S\)8<2RZ_��+=A]��,@F,@E&-/0KS7alO��9dp8amB~�E��P��N��'023T]]��?x�3U^C��6\gU��&.0D��7_iR��H��I��M��$(*?v�Z��X��-AG#$%[��8co[��W��C��'25?v�8bn%*+L��N��2PX)7;=p~(58^��P��4WaQ��T��0JRQ��T��I��6]hR��T��0LSF��9eqE��E��9gsF��C��#&'\��`��&/16\fB��A{�R��]��(47%,.*:>*9=9fr:gt7^iU��?w�Z��X��^��Q��H��)6:V��'034U^E��.EL.FMK��@z�S�tRNS*������� ���+����Ԑ,����=V� IDATx�˵C!��C|���^yR]�M��O]����0N�2���"���(0V�(�Y%PDT-~(m��!�K �Y�~���I�f{����a����3��Op&�Ф��x��#�j��ڶm�m��c)]m����)Ƨgf�hk���Ҡ�gg�Ǚ���+X���uiyVת�k���\[�:,�6��jIJ ;�"�;��X���f�������S����8=�o�;���(���ӥBk�e��\7p+m����N�<��Q�O����y�g�tt��o������V����s������&_�a���V~��?�*8�Q ;8��,��� f���1 �x��ק�*���A���a#����#�nP�i+��C�,�����_�Nb��ø��H�B*�Ҧ �L(^<�Á�L6pJ�P������%"�R,�9�e3eR�a1�( 11 | ��q�8َ��mK˱mƶm���yi!�ΪY�u���_��?i���+���A|�{���?��_En).J�D�<� 12 | ���Z\Ts�R*( ��J���uX/ 4J9��5�DE�4k�4�&i�V4������vsf:�g,���BC��$�����@�I_?<�! ^���ӽ����B�%L�w�FD1���(F���H�%0����؄(�0���'���N.0u�@�Y�PW�I�aN�K ���?�ӵ�=�e�v/c����0c0�2��:�06R-u�ĭ\Q̶�䴼�6R# �F��6��rՁ��u��m�����I�i~ ����ÏsP�"� � eiy��P����򒧡���,S]U��V��֔��Z��o���xz���Snm�{ںwaل��Ż���(mg/��������[�b��q��&կ��$�zȊ�H>a�KT1/��1O��0�.h͇Y�A��� 13 | -�>ۋ���Xբ�}ߨ� ��;��N��v���θ�1����O@&v/��_��\��\�.��+0�;!f� ���%� JY�O�Ž'/�]_�;��'"&N�n aQ�^�cx�A��IEND�B`� -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | KKT 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/client.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import App from './routes'; 5 | import { BrowserRouter } from "react-router-dom"; 6 | import store from "./models" 7 | import { Provider } from 'react-redux'; 8 | 9 | ReactDOM.hydrate( 10 | 11 | 12 | 13 | 14 | 15 | , document.getElementById('root')); 16 | // @ts-ignore 17 | if (module.hot) { 18 | // @ts-ignore 19 | module.hot.accept(); 20 | } 21 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/models/demo.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from "@rematch/core" 2 | import { RootModel } from "./index" 3 | 4 | export default createModel()({ 5 | name: "demo", 6 | state: { 7 | title: "demo 标题" 8 | }, 9 | effects: () => ({ 10 | async very(_, store) { 11 | console.log("打印 demo", store.demo.title) 12 | } 13 | }) 14 | }) -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/models/index.ts: -------------------------------------------------------------------------------- 1 | import login from "./login" 2 | import demo from "./demo" 3 | // @ts-ignore 4 | import { 5 | init, 6 | Models, 7 | Model, 8 | RematchRootState, 9 | RematchDispatch, 10 | } from '@rematch/core'; 11 | import loading, { ExtraModelsFromLoading } from '@rematch/loading'; 12 | 13 | export const models = { 14 | login, 15 | demo, 16 | } 17 | 18 | export interface RootModel extends Models { 19 | login: typeof login, 20 | demo: typeof demo, 21 | } 22 | export type FullModel = ExtraModelsFromLoading 23 | 24 | export const store = init({ 25 | models, 26 | plugins: [loading()], 27 | }); 28 | 29 | export const { dispatch, addModel } = store; 30 | export type Store = typeof store; 31 | export type AddModel = typeof addModel; 32 | export type Dispatch = RematchDispatch; 33 | export type RootState = RematchRootState; 34 | export type ModelDefault = Model; 35 | 36 | export default store; 37 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/models/login.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from "@rematch/core" 2 | import { RootModel } from "./index" 3 | 4 | export default createModel()({ 5 | name: "login", 6 | state: { 7 | title: "login 标题" 8 | }, 9 | effects: () => ({ 10 | async very(_, store) { 11 | console.log("打印 login", store.login.title) 12 | } 13 | }), 14 | reducers: { 15 | update(state, { payload }) { 16 | return { 17 | ...state, 18 | ...payload 19 | } 20 | } 21 | } 22 | }) -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/routes/About/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from 'react-redux' 3 | import { RootState } from "./../../models" 4 | const About = () => { 5 | const title = useSelector((store) => store.demo.title) 6 | return
About {title}
7 | } 8 | export default About; -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/routes/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from 'react-redux' 3 | import { RootState } from "./../../models" 4 | 5 | const Home = () => { 6 | const title = useSelector(({ login }) => login.title) 7 | 8 | const [count, setCount] = React.useState(1) 9 | 10 | const onClick = () => { 11 | console.log(title) 12 | setCount(pre => pre + 1) 13 | } 14 | 15 | return
home {title}
16 | } 17 | export default Home; -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Routes, Outlet, Link } from "react-router-dom"; 3 | import Home from "./Home" 4 | import About from "./About" 5 | 6 | const Nav = () => { 7 | return
8 | home 9 | about 10 | 11 |
12 | } 13 | 14 | export default () => { 15 | return 16 | } > 17 | } /> 18 | } /> 19 | } /> 20 | 无页面} /> 21 | 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/server.ts: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import app from './serverIndex'; 3 | 4 | const logs = console.log; // eslint-disable-line 5 | 6 | const server = http.createServer(app); 7 | let currentApp = app; 8 | const PORT = parseInt(process.env.PORT || "3000") + 1; 9 | const HOST = process.env.HOST || 'localhost'; 10 | // @ts-ignore 11 | server.listen(PORT, (error) => { 12 | if (error) { 13 | logs(error); 14 | } 15 | logs('🚀 started!', `PORT: http://${HOST}:${PORT}`); 16 | }); 17 | // @ts-ignore 18 | if (module.hot) { 19 | logs('✅ Server-side HMR Enabled!'); 20 | // @ts-ignore 21 | module.hot.accept('./serverIndex', () => { 22 | logs('🔁 HMR Reloading `./serverIndex`...'); 23 | server.removeListener('request', currentApp); 24 | const newApp = require('./serverIndex').default; // eslint-disable-line 25 | server.on('request', newApp); 26 | currentApp = newApp; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/src/serverIndex.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import express from 'express'; 3 | import { renderToString } from 'react-dom/server'; 4 | import { StaticRouter } from "react-router-dom/server"; 5 | import Path from 'path'; 6 | import FS from 'fs'; 7 | import { Provider } from 'react-redux'; 8 | import store from "./models" 9 | import App from './routes'; 10 | 11 | // @ts-ignore 12 | const assetsMainifest = new Function(`return ${FS.readFileSync(`${OUTPUT_PUBLIC_PATH}/asset-client-manifest.json`, "utf-8")}`)() 13 | 14 | 15 | const appDirectory = FS.realpathSync(process.cwd()); 16 | const resolveApp = (relativePath: string) => Path.resolve(appDirectory, relativePath); 17 | const server = express(); 18 | // server.use(express.static(resolveApp("dist"))) 19 | 20 | const render = (props: any = {}) => { 21 | const html = renderToString( 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | return html; 29 | }; 30 | 31 | server 32 | .disable('x-powered-by') 33 | .use(express.static(resolveApp('dist'))) 34 | .get('/*', (req, res) => { 35 | let urls = req.url 36 | if (urls === "/") { 37 | urls = "/home" 38 | } 39 | const context = {}; 40 | const markup = render({ url: urls }); 41 | if (req.url === "/") { 42 | res.redirect("/home"); 43 | } else { 44 | res.status(200).send(` 45 | 46 | 47 | 48 | 49 | 50 | Welcome to KKT 51 | 52 | ${assetsMainifest.client.css ? `` : ''} 53 | 54 | 55 |
${markup}
56 | ${assetsMainifest.client.js ? `` : ""} 57 | 58 | `); 59 | } 60 | }); 61 | 62 | export default server; 63 | -------------------------------------------------------------------------------- /example/basic-routes-rematch-new/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "esnext", 6 | "dom" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "declaration": true, 19 | "baseUrl": ".", 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true, 22 | "noEmit": true, 23 | "paths": { 24 | "@/*": [ 25 | "./src/*" 26 | ], 27 | } 28 | }, 29 | "include": [ 30 | "src/**/*", 31 | ".kktssrrc.ts" 32 | ] 33 | } -------------------------------------------------------------------------------- /example/basic-routes/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | package-lock.json 4 | coverage 5 | dist 6 | .DS_Store 7 | .cache 8 | .vscode 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | *.bak 15 | *.tem 16 | *.temp 17 | #.swp 18 | *.*~ 19 | ~*.* 20 | -------------------------------------------------------------------------------- /example/basic-routes/.kktssrrc.ts: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | export default { 3 | output_path: path.join(process.cwd(), "build") 4 | } -------------------------------------------------------------------------------- /example/basic-routes/README.md: -------------------------------------------------------------------------------- 1 | Basic Example 2 | --- 3 | 4 | The [react](https://github.com/facebook/react) base application. 5 | 6 | ## Development 7 | 8 | Runs the project in development mode. 9 | 10 | ```bash 11 | npm run start 12 | ``` 13 | 14 | Runs Node Server 15 | 16 | ```bash 17 | npm run server 18 | ``` 19 | 20 | ## production 21 | 22 | Builds the app for production to the build folder. 23 | 24 | ```bash 25 | npm run build 26 | ``` 27 | 28 | The build is minified and the filenames include the hashes. 29 | Your app is ready to be deployed! 30 | -------------------------------------------------------------------------------- /example/basic-routes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/basic-routes", 3 | "version": "3.1.13", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "server": "node build/server.js", 8 | "build": "kkt-ssr build", 9 | "start": "kkt-ssr start" 10 | }, 11 | "keywords": [], 12 | "dependencies": { 13 | "express": "~4.16.4", 14 | "react": "~17.0.2", 15 | "react-dom": "~17.0.2", 16 | "react-router": "^6.2.2", 17 | "react-router-dom": "^6.2.2" 18 | }, 19 | "devDependencies": { 20 | "@kkt/ssr": "3.1.13", 21 | "kkt": "~7.1.5" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/basic-routes/public/favicon.ico: -------------------------------------------------------------------------------- 1 |  �F )  95@@ �n�PNG 2 |  3 | IHDR(-S�PLTE""""""""""""""""""2PX=r�)7;*:>H��-BGE��8do5Xb6[eK��K��1MU9gs3S\I��:gt'03@{�V��T��A}�V��@y�6\fH��-CII��E��+;@7_i7_jF��J��K��H��-BHa��,@FC��L��&.0W��N��I��$)+B��J��R��?v�>s�>u�S��=qP��P��P��,?D4U^%+-M��K��%+,2OX+!�g��Hg��Eߏܵ} ݻ����kd���Jo���3�L"J������Q�$��ļff�,�5i9̟�H�/mB��w��w;D �+&�W����D�o@ʴRI��B�om.۳�IEND�B`��PNG 4 |  5 | IHDRש��ePLTE""""""""""""""""""""""""2RZN��J��3R[J��)59Y��0KS4W`Q��L��%+-0JR)6::gtC��"##?v�U��?w�u�S��/HNM��_��\��M��8doD��D��>t�+=B[��,>C>t�u�;kxG��R��/HN&-/@y�>s�>t�@z�]��P��$'(D��]��.''�`C$F�� �(�x"6X�c�T��L�@I�;dd-�|�P,Ȕ9�R���f3��F�VmM �X�����@Y�7����N�=� ���ʪu }֬�+�e�aiq ��76����=h �Z���l��}�ʱ�[F�I9A�k9��� ��3��9Ρ�qB~��b���U_�^��[��w��{z�v�z��(��(��(����f�q��G���k���Y���f���~�:*4�Q\O>�����<ד�W���Z|ދ�7���jT���n�����`$H�+�GO���*�x����X*|�^�dIEND�B`��PNG 9 |  10 | IHDR@@����:PLTE"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""%+-@y�W��`��^��S��C��,>C*8u�K��`��A}�L��8do=r�%+,@y�^��S��)59=q�P��U��"#$P��\��0JQQ��"##U��#&&_��>t�>s�`��_��5Yc1OW5Zd1NV+=B1MU+;@/GM\��*;?3S\)8<2RZ_��+=A]��,@F,@E&-/0KS7alO��9dp8amB~�E��P��N��'023T]]��?x�3U^C��6\gU��&.0D��7_iR��H��I��M��$(*?v�Z��X��-AG#$%[��8co[��W��C��'25?v�8bn%*+L��N��2PX)7;=p~(58^��P��4WaQ��T��0JRQ��T��I��6]hR��T��0LSF��9eqE��E��9gsF��C��#&'\��`��&/16\fB��A{�R��]��(47%,.*:>*9=9fr:gt7^iU��?w�Z��X��^��Q��H��)6:V��'034U^E��.EL.FMK��@z�S�tRNS*������� ���+����Ԑ,����=V� IDATx�˵C!��C|���^yR]�M��O]����0N�2���"���(0V�(�Y%PDT-~(m��!�K �Y�~���I�f{����a����3��Op&�Ф��x��#�j��ڶm�m��c)]m����)Ƨgf�hk���Ҡ�gg�Ǚ���+X���uiyVת�k���\[�:,�6��jIJ ;�"�;��X���f�������S����8=�o�;���(���ӥBk�e��\7p+m����N�<��Q�O����y�g�tt��o������V����s������&_�a���V~��?�*8�Q ;8��,��� f���1 �x��ק�*���A���a#����#�nP�i+��C�,�����_�Nb��ø��H�B*�Ҧ �L(^<�Á�L6pJ�P������%"�R,�9�e3eR�a1�( 11 | ��q�8َ��mK˱mƶm���yi!�ΪY�u���_��?i���+���A|�{���?��_En).J�D�<� 12 | ���Z\Ts�R*( ��J���uX/ 4J9��5�DE�4k�4�&i�V4������vsf:�g,���BC��$�����@�I_?<�! ^���ӽ����B�%L�w�FD1���(F���H�%0����؄(�0���'���N.0u�@�Y�PW�I�aN�K ���?�ӵ�=�e�v/c����0c0�2��:�06R-u�ĭ\Q̶�䴼�6R# �F��6��rՁ��u��m�����I�i~ ����ÏsP�"� � eiy��P����򒧡���,S]U��V��֔��Z��o���xz���Snm�{ںwaل��Ż���(mg/��������[�b��q��&կ��$�zȊ�H>a�KT1/��1O��0�.h͇Y�A��� 13 | -�>ۋ���Xբ�}ߨ� ��;��N��v���θ�1����O@&v/��_��\��\�.��+0�;!f� ���%� JY�O�Ž'/�]_�;��'"&N�n aQ�^�cx�A��IEND�B`� -------------------------------------------------------------------------------- /example/basic-routes/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | KKT 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example/basic-routes/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /example/basic-routes/src/client.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from "react-router-dom" 4 | // import { BrowserRouter } from "react-router" 5 | import App from './routes/index'; 6 | 7 | ReactDOM.hydrate(, document.getElementById('root')); 8 | 9 | // @ts-ignore 10 | if (module.hot) { 11 | // @ts-ignore 12 | module.hot.accept(); 13 | } 14 | -------------------------------------------------------------------------------- /example/basic-routes/src/routes/About/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const About = () => { 4 | 5 | return
About
6 | } 7 | export default About; -------------------------------------------------------------------------------- /example/basic-routes/src/routes/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Home = () => { 4 | return
home
5 | } 6 | export default Home; -------------------------------------------------------------------------------- /example/basic-routes/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Routes, Outlet, Link } from "react-router-dom"; 3 | import Home from "./Home" 4 | import About from "./About" 5 | 6 | const Nav = () => { 7 | return
8 | home 9 | about 10 | 11 |
12 | } 13 | 14 | export default () => { 15 | return 16 | } > 17 | } /> 18 | } /> 19 | } /> 20 | 无页面} /> 21 | 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /example/basic-routes/src/server.ts: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import app from './serverIndex'; 3 | 4 | const logs = console.log; // eslint-disable-line 5 | 6 | const server = http.createServer(app); 7 | let currentApp = app; 8 | const PORT = parseInt(process.env.PORT || "3000") + 1; 9 | const HOST = process.env.HOST || 'localhost'; 10 | // @ts-ignore 11 | server.listen(PORT, (error) => { 12 | if (error) { 13 | logs(error); 14 | } 15 | logs('🚀 started!', `PORT: http://${HOST}:${PORT}`); 16 | }); 17 | // @ts-ignore 18 | if (module.hot) { 19 | logs('✅ Server-side HMR Enabled!'); 20 | // @ts-ignore 21 | module.hot.accept('./serverIndex', () => { 22 | logs('🔁 HMR Reloading `./serverIndex`...'); 23 | server.removeListener('request', currentApp); 24 | const newApp = require('./serverIndex').default; // eslint-disable-line 25 | server.on('request', newApp); 26 | currentApp = newApp; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /example/basic-routes/src/serverIndex.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import express from 'express'; 3 | import { renderToString } from 'react-dom/server'; 4 | import { StaticRouter } from "react-router-dom/server"; 5 | import Path from 'path'; 6 | import FS from 'fs'; 7 | 8 | import App from './routes'; 9 | // @ts-ignore 10 | const assetsMainifest = new Function(`return ${FS.readFileSync(`${OUTPUT_PUBLIC_PATH}/asset-client-manifest.json`, "utf-8")}`)() 11 | 12 | 13 | const appDirectory = FS.realpathSync(process.cwd()); 14 | const resolveApp = (relativePath: string) => Path.resolve(appDirectory, relativePath); 15 | const server = express(); 16 | 17 | const render = (props: any = {}) => { 18 | const html = renderToString( 19 | 20 | 21 | 22 | ); 23 | return html; 24 | }; 25 | 26 | server 27 | .disable('x-powered-by') 28 | .use(express.static(resolveApp('build'))) 29 | .get('/*', (req, res) => { 30 | let urls = req.url 31 | if (urls === "/") { 32 | urls = "/home" 33 | } 34 | const context = {}; 35 | const markup = render({ url: urls }); 36 | if (req.url === "/") { 37 | res.redirect("/home"); 38 | } else { 39 | res.status(200).send(` 40 | 41 | 42 | 43 | 44 | 45 | Welcome to KKT 46 | 47 | ${assetsMainifest.client.css ? `` : ''} 48 | 49 | 50 |
${markup}
51 | ${assetsMainifest.client.js ? `` : ""} 52 | 53 | `); 54 | } 55 | }); 56 | 57 | export default server; 58 | -------------------------------------------------------------------------------- /example/basic-routes/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "esnext", 6 | "dom" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "declaration": true, 19 | "baseUrl": ".", 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true, 22 | "noEmit": true, 23 | "paths": { 24 | "@/*": [ 25 | "./src/*" 26 | ], 27 | } 28 | }, 29 | "include": [ 30 | "src/**/*", 31 | ".kktssrrc.ts" 32 | ] 33 | } -------------------------------------------------------------------------------- /example/basic/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | package-lock.json 4 | coverage 5 | dist 6 | .DS_Store 7 | .cache 8 | .vscode 9 | .env.local 10 | .env.development.local 11 | .env.test.local 12 | .env.production.local 13 | 14 | *.bak 15 | *.tem 16 | *.temp 17 | #.swp 18 | *.*~ 19 | ~*.* 20 | -------------------------------------------------------------------------------- /example/basic/README.md: -------------------------------------------------------------------------------- 1 | Basic Example 2 | --- 3 | 4 | The [react](https://github.com/facebook/react) base application. 5 | 6 | ## Development 7 | 8 | Runs the project in development mode. 9 | 10 | ```bash 11 | npm run start 12 | ``` 13 | 14 | Runs Node Server 15 | 16 | ```bash 17 | npm run server 18 | ``` 19 | 20 | ## production 21 | 22 | Builds the app for production to the build folder. 23 | 24 | ```bash 25 | npm run build 26 | ``` 27 | 28 | The build is minified and the filenames include the hashes. 29 | Your app is ready to be deployed! 30 | -------------------------------------------------------------------------------- /example/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/basic-ts", 3 | "version": "3.1.13", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "server": "node dist/server.js", 8 | "build": "kkt-ssr build", 9 | "start": "kkt-ssr start" 10 | }, 11 | "keywords": [], 12 | "dependencies": { 13 | "express": "~4.16.4", 14 | "react": "~17.0.2", 15 | "react-dom": "~17.0.2" 16 | }, 17 | "devDependencies": { 18 | "@kkt/ssr": "3.1.13", 19 | "@types/express": "~4.17.13", 20 | "@types/react": "~17.0.43", 21 | "@types/react-dom": "~17.0.14", 22 | "kkt": "~7.1.5" 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/basic/public/favicon.ico: -------------------------------------------------------------------------------- 1 |  �F )  95@@ �n�PNG 2 |  3 | IHDR(-S�PLTE""""""""""""""""""2PX=r�)7;*:>H��-BGE��8do5Xb6[eK��K��1MU9gs3S\I��:gt'03@{�V��T��A}�V��@y�6\fH��-CII��E��+;@7_i7_jF��J��K��H��-BHa��,@FC��L��&.0W��N��I��$)+B��J��R��?v�>s�>u�S��=qP��P��P��,?D4U^%+-M��K��%+,2OX+!�g��Hg��Eߏܵ} ݻ����kd���Jo���3�L"J������Q�$��ļff�,�5i9̟�H�/mB��w��w;D �+&�W����D�o@ʴRI��B�om.۳�IEND�B`��PNG 4 |  5 | IHDRש��ePLTE""""""""""""""""""""""""2RZN��J��3R[J��)59Y��0KS4W`Q��L��%+-0JR)6::gtC��"##?v�U��?w�u�S��/HNM��_��\��M��8doD��D��>t�+=B[��,>C>t�u�;kxG��R��/HN&-/@y�>s�>t�@z�]��P��$'(D��]��.''�`C$F�� �(�x"6X�c�T��L�@I�;dd-�|�P,Ȕ9�R���f3��F�VmM �X�����@Y�7����N�=� ���ʪu }֬�+�e�aiq ��76����=h �Z���l��}�ʱ�[F�I9A�k9��� ��3��9Ρ�qB~��b���U_�^��[��w��{z�v�z��(��(��(����f�q��G���k���Y���f���~�:*4�Q\O>�����<ד�W���Z|ދ�7���jT���n�����`$H�+�GO���*�x����X*|�^�dIEND�B`��PNG 9 |  10 | IHDR@@����:PLTE"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""%+-@y�W��`��^��S��C��,>C*8u�K��`��A}�L��8do=r�%+,@y�^��S��)59=q�P��U��"#$P��\��0JQQ��"##U��#&&_��>t�>s�`��_��5Yc1OW5Zd1NV+=B1MU+;@/GM\��*;?3S\)8<2RZ_��+=A]��,@F,@E&-/0KS7alO��9dp8amB~�E��P��N��'023T]]��?x�3U^C��6\gU��&.0D��7_iR��H��I��M��$(*?v�Z��X��-AG#$%[��8co[��W��C��'25?v�8bn%*+L��N��2PX)7;=p~(58^��P��4WaQ��T��0JRQ��T��I��6]hR��T��0LSF��9eqE��E��9gsF��C��#&'\��`��&/16\fB��A{�R��]��(47%,.*:>*9=9fr:gt7^iU��?w�Z��X��^��Q��H��)6:V��'034U^E��.EL.FMK��@z�S�tRNS*������� ���+����Ԑ,����=V� IDATx�˵C!��C|���^yR]�M��O]����0N�2���"���(0V�(�Y%PDT-~(m��!�K �Y�~���I�f{����a����3��Op&�Ф��x��#�j��ڶm�m��c)]m����)Ƨgf�hk���Ҡ�gg�Ǚ���+X���uiyVת�k���\[�:,�6��jIJ ;�"�;��X���f�������S����8=�o�;���(���ӥBk�e��\7p+m����N�<��Q�O����y�g�tt��o������V����s������&_�a���V~��?�*8�Q ;8��,��� f���1 �x��ק�*���A���a#����#�nP�i+��C�,�����_�Nb��ø��H�B*�Ҧ �L(^<�Á�L6pJ�P������%"�R,�9�e3eR�a1�( 11 | ��q�8َ��mK˱mƶm���yi!�ΪY�u���_��?i���+���A|�{���?��_En).J�D�<� 12 | ���Z\Ts�R*( ��J���uX/ 4J9��5�DE�4k�4�&i�V4������vsf:�g,���BC��$�����@�I_?<�! ^���ӽ����B�%L�w�FD1���(F���H�%0����؄(�0���'���N.0u�@�Y�PW�I�aN�K ���?�ӵ�=�e�v/c����0c0�2��:�06R-u�ĭ\Q̶�䴼�6R# �F��6��rՁ��u��m�����I�i~ ����ÏsP�"� � eiy��P����򒧡���,S]U��V��֔��Z��o���xz���Snm�{ںwaل��Ż���(mg/��������[�b��q��&կ��$�zȊ�H>a�KT1/��1O��0�.h͇Y�A��� 13 | -�>ۋ���Xբ�}ߨ� ��;��N��v���θ�1����O@&v/��_��\��\�.��+0�;!f� ���%� JY�O�Ž'/�]_�;��'"&N�n aQ�^�cx�A��IEND�B`� -------------------------------------------------------------------------------- /example/basic/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | KKT 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example/basic/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /example/basic/src/app/App.module.css: -------------------------------------------------------------------------------- 1 | 2 | .warpper { 3 | border: 1px solid red; 4 | padding-left: 220px; 5 | } -------------------------------------------------------------------------------- /example/basic/src/app/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | describe('', () => { 6 | test('renders without exploding', () => { 7 | ReactDOM.render(, document.createElement('div')); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /example/basic/src/app/App.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import styles from './App.module.css'; 3 | import './app.css'; 4 | 5 | const App = () => ( 6 |
7 | Welcome to KKT. 8 |
Btn
9 |
10 | ); 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /example/basic/src/app/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 100px; 3 | font-family: sans-serif; 4 | } 5 | 6 | .btn { 7 | color: red; 8 | } -------------------------------------------------------------------------------- /example/basic/src/client.tsx: -------------------------------------------------------------------------------- 1 | import { hydrate } from 'react-dom'; 2 | import App from './app/App'; 3 | 4 | hydrate(, document.getElementById('root')); 5 | // @ts-ignore 6 | if (module.hot) { 7 | // @ts-ignore 8 | module.hot.accept(); 9 | } 10 | -------------------------------------------------------------------------------- /example/basic/src/server.ts: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import app from './serverIndex'; 3 | 4 | const logs = console.log; // eslint-disable-line 5 | 6 | const server = http.createServer(app); 7 | let currentApp = app; 8 | const PORT = parseInt(process.env.PORT || "3000") + 1; 9 | const HOST = process.env.HOST || 'localhost'; 10 | // @ts-ignore 11 | server.listen(PORT, (error) => { 12 | if (error) { 13 | logs(error); 14 | } 15 | logs('🚀 started!', `PORT: http://${HOST}:${PORT}`); 16 | }); 17 | // @ts-ignore 18 | if (module.hot) { 19 | logs('✅ Server-side HMR Enabled!'); 20 | // @ts-ignore 21 | module.hot.accept('./serverIndex', () => { 22 | logs('🔁 HMR Reloading `./serverIndex`...'); 23 | server.removeListener('request', currentApp); 24 | const newApp = require('./serverIndex').default; // eslint-disable-line 25 | server.on('request', newApp); 26 | currentApp = newApp; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /example/basic/src/serverIndex.tsx: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { renderToString } from 'react-dom/server'; 3 | import App from './app/App'; 4 | 5 | import Path from 'path'; 6 | import FS from 'fs'; 7 | 8 | // @ts-ignore 9 | const assetsMainifest = new Function(`return ${FS.readFileSync(`${OUTPUT_PUBLIC_PATH}/asset-client-manifest.json`, "utf-8")}`)() 10 | 11 | 12 | const appDirectory = FS.realpathSync(process.cwd()); 13 | const resolveApp = (relativePath: string) => Path.resolve(appDirectory, relativePath); 14 | const server = express(); 15 | 16 | server 17 | .disable('x-powered-by') 18 | .use(express.static(resolveApp('dist'))) 19 | .get('/*', (req, res) => { 20 | const markup = renderToString(); 21 | res.status(200).send(` 22 | 23 | 24 | 25 | 26 | 27 | Welcome to KKT 28 | 29 | ${assetsMainifest.client.css ? `` : ''} 30 | 31 | 32 |
${markup}
33 | ${assetsMainifest.client.js ? `` : ""} 34 | 35 | `); 36 | }); 37 | 38 | export default server; 39 | -------------------------------------------------------------------------------- /example/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "esnext", 6 | "dom" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "declaration": true, 19 | "baseUrl": ".", 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true, 22 | "noEmit": true, 23 | "paths": { 24 | "@/*": [ 25 | "./src/*" 26 | ], 27 | "@@/*": [ 28 | "./src/.uiw/*" 29 | ] 30 | } 31 | }, 32 | "include": [ 33 | "src/**/*", 34 | ".kktrc.ts" 35 | ] 36 | } -------------------------------------------------------------------------------- /example/basic/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | package-lock.json 4 | yarn.lock 5 | coverage 6 | dist 7 | .DS_Store 8 | .cache 9 | .vscode 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | *.log 16 | *.bak 17 | *.tem 18 | *.temp 19 | #.swp 20 | *.*~ 21 | ~*.* 22 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/.kktssrrc.ts: -------------------------------------------------------------------------------- 1 | import pluginLess from "@kkt/plugin-less" 2 | import { Options } from "@kkt/ssr/lib/interface" 3 | import webpack from "webpack" 4 | import { Application } from "express" 5 | 6 | export default { 7 | GENERATE_SOURCEMAP: JSON.stringify(false), 8 | proxySetup: (app: Application) => ({ 9 | path: "./mocker/index.js", 10 | }), 11 | overridesServerWebpack: (conf: webpack.Configuration) => { 12 | conf.plugins?.push(new webpack.DefinePlugin({ 13 | window: JSON.stringify(undefined) 14 | })) 15 | return conf 16 | }, 17 | overridesClientWebpack: (conf: webpack.Configuration, env: "production" | "development", options: Options): webpack.Configuration => { 18 | return { 19 | ...conf, 20 | resolve: { 21 | ...conf.resolve, 22 | fallback: { 23 | "util": require.resolve("util/"), 24 | "crypto": require.resolve("crypto-browserify"), 25 | "stream": require.resolve("stream-browserify"), 26 | "http": require.resolve("stream-http"), 27 | "https": require.resolve("https-browserify"), 28 | "url": require.resolve("url/"), 29 | "zlib": require.resolve("browserify-zlib"), 30 | "tty": require.resolve("tty-browserify"), 31 | "assert": require.resolve("assert/"), 32 | "path": require.resolve("path-browserify"), 33 | "os": require.resolve("os-browserify/browser"), 34 | "buffer": require.resolve("buffer/") 35 | }, 36 | } 37 | } 38 | }, 39 | overridesCommonWebpack: (conf: webpack.Configuration, env: "production" | "development", options: Options): webpack.Configuration => { 40 | const newConfig = pluginLess(conf, { 41 | target: conf.target === "node14" ? "node" : "web", 42 | env, 43 | paths: options.paths 44 | }) 45 | return newConfig 46 | }, 47 | 48 | } -------------------------------------------------------------------------------- /example/react-router-rematch-old/README.md: -------------------------------------------------------------------------------- 1 | react-router+rematch 2 | --- 3 | 4 | A simple for server side rendering for your React application. This is a basic, bare-bones example of how to use [`@kkt/react-ssr-enhanced`](https://github.com/kktjs/ssr/tree/master/packages) and [`@kkt/ssr`](https://github.com/kktjs/ssr). 5 | 6 | [Rematch](https://github.com/rematch/rematch) is [Redux](https://github.com/reduxjs/redux) best practices without the boilerplate. No more action types, action creators, switch statements or thunks. 7 | 8 | [`React`](https://github.com/facebook/react) + [`React Router`](https://github.com/ReactTraining/react-router) + [`Rematch`](https://github.com/rematch/rematch) + [`Express`](https://expressjs.com/) 9 | 10 | ## Quick Start 11 | 12 | > ⚠️ A perfect example `react-router+rematch` is recommended for production environments. 13 | 14 | ```bash 15 | npx create-kkt-app my-app -e react-router+rematch 16 | cd my-app 17 | npm start 18 | ``` 19 | 20 | **development** 21 | 22 | Runs the project in development mode. 23 | 24 | ```bash 25 | npm install 26 | npm run start 27 | ``` 28 | 29 | **production** 30 | 31 | Builds the app for production to the build folder. 32 | 33 | ```bash 34 | npm run build 35 | ``` 36 | 37 | The build is minified and the filenames include the hashes. 38 | Your app is ready to be deployed! 39 | 40 | Runs the compiled app in production. 41 | 42 | ```bash 43 | npm run server 44 | ``` 45 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/mocker/index.js: -------------------------------------------------------------------------------- 1 | 2 | const proxy = { 3 | 'GET /api/user/:id': { id: 1, username: 'kenny', sex: 6 }, 4 | 'GET /api/user/verify': { 5 | token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', 6 | id: 13, 7 | username: 'kenny', 8 | sex: 'male', 9 | }, 10 | } 11 | module.exports = proxy 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@examples/basic-routes-rematch-old", 3 | "version": "3.1.13", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "server": "NODE_ENV=production nodemon dist/server.js", 8 | "build": "kkt-ssr build --s-ne", 9 | "start": "kkt-ssr start", 10 | "build:s": "kkt-ssr build --s-st" 11 | }, 12 | "keywords": [], 13 | "author": "Kenny Wong ", 14 | "license": "MIT", 15 | "dependencies": { 16 | "@kkt/react-ssr-enhanced": "3.1.13", 17 | "@rematch/core": "2.2.0", 18 | "@rematch/loading": "2.1.2", 19 | "axios": "0.26.0", 20 | "cookie-parser": "1.4.3", 21 | "cookiejs": "1.0.15", 22 | "express": "~4.16.4", 23 | "http-proxy-middleware": "~0.19.1", 24 | "react": "~17.0.2", 25 | "react-dom": "~17.0.2", 26 | "react-dynamic-loadable": "~3.0.0", 27 | "react-redux": "7.2.6", 28 | "react-router": "^6.2.2", 29 | "react-router-dom": "^6.2.2", 30 | "redux": "^4.1.2" 31 | }, 32 | "devDependencies": { 33 | "@kkt/plugin-less": "3.1.13", 34 | "@kkt/ssr": "3.1.13", 35 | "@types/cookie-parser": "^1.4.2", 36 | "@types/cookiejs": "^1.2.0", 37 | "@types/react-helmet": "^6.1.5", 38 | "assert": "2.0.0", 39 | "browserify-zlib": "0.2.0", 40 | "buffer": "6.0.3", 41 | "crypto-browserify": "3.12.0", 42 | "https-browserify": "1.0.0", 43 | "kkt": "~7.1.5", 44 | "mocker-api": "2.9.5", 45 | "os-browserify": "0.3.0", 46 | "path-browserify": "1.0.1", 47 | "stream-browserify": "3.0.0", 48 | "stream-http": "3.2.0", 49 | "tty-browserify": "0.0.1", 50 | "url": "0.11.0", 51 | "util": "0.12.4" 52 | }, 53 | "browserslist": { 54 | "production": [ 55 | ">0.2%", 56 | "not dead", 57 | "not op_mini all" 58 | ], 59 | "development": [ 60 | "last 1 chrome version", 61 | "last 1 firefox version", 62 | "last 1 safari version" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/public/favicon.ico: -------------------------------------------------------------------------------- 1 |  �F )  95@@ �n�PNG 2 |  3 | IHDR(-S�PLTE""""""""""""""""""2PX=r�)7;*:>H��-BGE��8do5Xb6[eK��K��1MU9gs3S\I��:gt'03@{�V��T��A}�V��@y�6\fH��-CII��E��+;@7_i7_jF��J��K��H��-BHa��,@FC��L��&.0W��N��I��$)+B��J��R��?v�>s�>u�S��=qP��P��P��,?D4U^%+-M��K��%+,2OX+!�g��Hg��Eߏܵ} ݻ����kd���Jo���3�L"J������Q�$��ļff�,�5i9̟�H�/mB��w��w;D �+&�W����D�o@ʴRI��B�om.۳�IEND�B`��PNG 4 |  5 | IHDRש��ePLTE""""""""""""""""""""""""2RZN��J��3R[J��)59Y��0KS4W`Q��L��%+-0JR)6::gtC��"##?v�U��?w�u�S��/HNM��_��\��M��8doD��D��>t�+=B[��,>C>t�u�;kxG��R��/HN&-/@y�>s�>t�@z�]��P��$'(D��]��.''�`C$F�� �(�x"6X�c�T��L�@I�;dd-�|�P,Ȕ9�R���f3��F�VmM �X�����@Y�7����N�=� ���ʪu }֬�+�e�aiq ��76����=h �Z���l��}�ʱ�[F�I9A�k9��� ��3��9Ρ�qB~��b���U_�^��[��w��{z�v�z��(��(��(����f�q��G���k���Y���f���~�:*4�Q\O>�����<ד�W���Z|ދ�7���jT���n�����`$H�+�GO���*�x����X*|�^�dIEND�B`��PNG 9 |  10 | IHDR@@����:PLTE"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""%+-@y�W��`��^��S��C��,>C*8u�K��`��A}�L��8do=r�%+,@y�^��S��)59=q�P��U��"#$P��\��0JQQ��"##U��#&&_��>t�>s�`��_��5Yc1OW5Zd1NV+=B1MU+;@/GM\��*;?3S\)8<2RZ_��+=A]��,@F,@E&-/0KS7alO��9dp8amB~�E��P��N��'023T]]��?x�3U^C��6\gU��&.0D��7_iR��H��I��M��$(*?v�Z��X��-AG#$%[��8co[��W��C��'25?v�8bn%*+L��N��2PX)7;=p~(58^��P��4WaQ��T��0JRQ��T��I��6]hR��T��0LSF��9eqE��E��9gsF��C��#&'\��`��&/16\fB��A{�R��]��(47%,.*:>*9=9fr:gt7^iU��?w�Z��X��^��Q��H��)6:V��'034U^E��.EL.FMK��@z�S�tRNS*������� ���+����Ԑ,����=V� IDATx�˵C!��C|���^yR]�M��O]����0N�2���"���(0V�(�Y%PDT-~(m��!�K �Y�~���I�f{����a����3��Op&�Ф��x��#�j��ڶm�m��c)]m����)Ƨgf�hk���Ҡ�gg�Ǚ���+X���uiyVת�k���\[�:,�6��jIJ ;�"�;��X���f�������S����8=�o�;���(���ӥBk�e��\7p+m����N�<��Q�O����y�g�tt��o������V����s������&_�a���V~��?�*8�Q ;8��,��� f���1 �x��ק�*���A���a#����#�nP�i+��C�,�����_�Nb��ø��H�B*�Ҧ �L(^<�Á�L6pJ�P������%"�R,�9�e3eR�a1�( 11 | ��q�8َ��mK˱mƶm���yi!�ΪY�u���_��?i���+���A|�{���?��_En).J�D�<� 12 | ���Z\Ts�R*( ��J���uX/ 4J9��5�DE�4k�4�&i�V4������vsf:�g,���BC��$�����@�I_?<�! ^���ӽ����B�%L�w�FD1���(F���H�%0����؄(�0���'���N.0u�@�Y�PW�I�aN�K ���?�ӵ�=�e�v/c����0c0�2��:�06R-u�ĭ\Q̶�䴼�6R# �F��6��rՁ��u��m�����I�i~ ����ÏsP�"� � eiy��P����򒧡���,S]U��V��֔��Z��o���xz���Snm�{ںwaل��Ż���(mg/��������[�b��q��&կ��$�zȊ�H>a�KT1/��1O��0�.h͇Y�A��� 13 | -�>ۋ���Xբ�}ߨ� ��;��N��v���θ�1����O@&v/��_��\��\�.��+0�;!f� ���%� JY�O�Ž'/�]_�;��'"&N�n aQ�^�cx�A��IEND�B`� -------------------------------------------------------------------------------- /example/react-router-rematch-old/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/client.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import { Provider } from 'react-redux'; 5 | import { ensureReady, RoutersController } from '@kkt/react-ssr-enhanced'; 6 | import history from './utils/history'; 7 | import { getRouterData } from './routes'; 8 | import { createStore } from './store'; 9 | 10 | declare global { 11 | interface Window { 12 | _KKT_STORE: any; 13 | _history: typeof history, 14 | } 15 | } 16 | 17 | const routes = getRouterData(); 18 | (async () => { 19 | const store = await createStore(window._KKT_STORE); 20 | // Initialize store 21 | ensureReady(routes).then(async (data) => { 22 | // Fix: Expected server HTML to contain a matching in 23 | // Warning: render(): Calling ReactDOM.render() to hydrate server-rendered markup will stop working in React v17. 24 | // Replace the ReactDOM.render() call with ReactDOM.hydrate() if you want React to attach to the server HTML. 25 | // const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate; // eslint-disable-line 26 | // const renderMethod = !!module.hot ? ReactDOM.render : ReactDOM.hydrate; // eslint-disable-line 27 | const renderMethod = ReactDOM.hydrate; // eslint-disable-line 28 | // The server renders an error and is mounted on the Window object. 29 | // The object exists only on the client side. 30 | window._history = history; 31 | renderMethod( 32 | 33 | 34 | 35 | 36 | , 37 | document.getElementById('root') 38 | ); 39 | }); 40 | })(); 41 | 42 | // @ts-ignore 43 | if (module.hot) { 44 | // @ts-ignore 45 | module.hot.accept(); 46 | } 47 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/components/Container/index.css: -------------------------------------------------------------------------------- 1 | 2 | .red { 3 | color: red; 4 | } 5 | 6 | .title { 7 | margin: 0; 8 | padding: 10px 0; 9 | } 10 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/components/Container/index.module.css: -------------------------------------------------------------------------------- 1 | .home { 2 | text-align: center; 3 | } 4 | 5 | .logo { 6 | animation: logo-spin infinite 20s linear; 7 | height: 80px; 8 | width: 80px; 9 | } 10 | 11 | .header { 12 | background-color: #222; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .menus a { 18 | color: #fff; 19 | } 20 | 21 | .menus a + a{ 22 | margin-left: 10px; 23 | } 24 | 25 | @keyframes logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/components/Container/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import logo from './react.svg'; 4 | import styles from './index.module.css'; 5 | import './index.css'; 6 | 7 | interface ContainerProps { 8 | title: string; 9 | children: React.ReactNode; 10 | } 11 | 12 | const Container = (props: ContainerProps) => { 13 | const { title } = props; 14 | return ( 15 |
16 |
17 | logo 18 |

{title}

19 |
20 | Home 21 | About 22 | Repos 23 | Repos Detail 24 | No Match 25 |
26 |
27 | {props.children} 28 |
29 | ); 30 | } 31 | export default Container -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/models/about.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from "@rematch/core" 2 | import { RootModel } from "./index" 3 | export default createModel()({ 4 | state: { 5 | test: 'abouts state test', 6 | }, 7 | reducers: { 8 | updateState: (state, payload) => ({ ...state, ...payload }), 9 | }, 10 | effects: { 11 | async verify() { 12 | // console.log(rootState); // eslint-disable-line 13 | // this.updateState({ test: 'test111' }); 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/models/global.ts: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | import { createModel } from "@rematch/core" 3 | import { RootModel } from "./index" 4 | export default createModel()({ 5 | state: { 6 | test: 'global state test', 7 | name: 'kkt ssr', 8 | token: null, 9 | userinfo: null, 10 | }, 11 | reducers: { 12 | verify: (state, payload) => ({ ...state, ...payload }), 13 | updateState: (state, payload) => ({ ...state, ...payload }), 14 | }, 15 | effects: () => ({ 16 | // async verify({ token }, { global }) { 17 | // const verify = await request('/api/user/verify', { body: { token } }); 18 | // const state = { ...global, test: 'test:global:111----------->' }; 19 | // state.token = verify ? token : null; 20 | // state.userinfo = verify; 21 | // await this.updateState({ ...state }); 22 | // }, 23 | }), 24 | }); 25 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/models/home.ts: -------------------------------------------------------------------------------- 1 | import { createModel } from "@rematch/core" 2 | import { RootModel } from "./index" 3 | export default createModel()({ 4 | state: { 5 | test: 'home state test', 6 | }, 7 | reducers: { 8 | updateState: (state, payload) => ({ ...state, ...payload }), 9 | }, 10 | effects: {}, 11 | }); 12 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/models/index.ts: -------------------------------------------------------------------------------- 1 | 2 | // @ts-ignore 3 | import { 4 | init, 5 | Models, 6 | Model, 7 | RematchRootState, 8 | RematchDispatch, 9 | } from '@rematch/core'; 10 | import loading, { ExtraModelsFromLoading } from '@rematch/loading'; 11 | 12 | import home from "./home" 13 | import global from "./global" 14 | import about from "./about" 15 | import cookie from 'cookiejs'; 16 | 17 | export const models = { 18 | home, 19 | global, 20 | about, 21 | } 22 | 23 | export interface RootModel extends Models { 24 | home: typeof home, 25 | global: typeof global, 26 | about: typeof about, 27 | } 28 | 29 | export type FullModel = ExtraModelsFromLoading 30 | 31 | export const store = init({ 32 | models, 33 | plugins: [ 34 | loading(), 35 | { 36 | createMiddleware: () => () => next => async (action) => { 37 | if (typeof window !== 'undefined') { 38 | const token = cookie.get('token'); 39 | if (token) { 40 | await cookie.set('token', token, 1); 41 | } 42 | } 43 | return next(action); 44 | }, 45 | }], 46 | }); 47 | 48 | export const { dispatch, addModel } = store; 49 | export type Store = typeof store; 50 | export type AddModel = typeof addModel; 51 | export type Dispatch = RematchDispatch; 52 | export type RootState = RematchRootState; 53 | export type ModelDefault = Model; 54 | 55 | export default store; 56 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/about/index.css: -------------------------------------------------------------------------------- 1 | 2 | .red { 3 | color: red; 4 | } 5 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/about/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Helmet } from 'react-helmet'; 4 | import Container from '../../components/Container'; 5 | import './index.css'; 6 | import { RootState, Dispatch } from "./../../models" 7 | class About extends React.Component<{ test: string }> { 8 | render() { 9 | return ( 10 | 11 | 12 | AboutSSS 13 | 14 |
15 | About
16 | {this.props.test} 17 |
18 |
19 | ); 20 | } 21 | } 22 | 23 | const mapState = ({ about }: RootState) => ({ 24 | test: about.test, 25 | }); 26 | 27 | const mapDispatch = ({ global }: Dispatch) => ({ 28 | verify: global.verify, 29 | }); 30 | 31 | export default connect(mapState, mapDispatch)(About); 32 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/home/home.module.css: -------------------------------------------------------------------------------- 1 | .resources { 2 | list-style: none; 3 | } 4 | 5 | .resources > li { 6 | display: inline-block; 7 | padding: 1rem; 8 | } 9 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import { Helmet } from 'react-helmet'; 5 | import cookie from 'cookiejs'; 6 | import Container from '../../components/Container'; 7 | import styles from './home.module.css'; 8 | import { GetInitialProps } from "@kkt/react-ssr-enhanced" 9 | import { RootState, Dispatch } from "./../../models" 10 | 11 | class Home extends React.Component<{ test: string, testHome: string, whatever?: string }> { 12 | 13 | static getInitialProps = async (ctx: GetInitialProps) => { 14 | const { req, res, match, store, history, location } = ctx 15 | let token = null; 16 | // // only on the server side 17 | if (req && store.dispatch.global && store.dispatch.global.verify && req.cookies) { 18 | token = req.cookies.token; 19 | } 20 | if (window && typeof window !== 'undefined') { 21 | token = cookie.get('token'); 22 | } 23 | await store.dispatch.global.verify({ token: 2121 }) 24 | return Promise.resolve({ 25 | whatever: 'Home stuff', isServer: true 26 | }) 27 | } 28 | 29 | 30 | render() { 31 | return ( 32 | 33 | 34 | HomeSSS 35 | 36 |
37 | {this.props.test}
38 | {this.props.testHome}
39 | {this.props.whatever ? this.props.whatever : 'loading'} 40 |
41 |
52 | 53 | ); 54 | } 55 | } 56 | 57 | const mapState = ({ global, home }: RootState) => ({ 58 | test: global.test, 59 | testHome: home.test, 60 | name: global.name, 61 | }); 62 | 63 | const mapDispatch = ({ global }: Dispatch) => ({ 64 | verify: global.verify, 65 | }); 66 | 67 | export default connect(mapState, mapDispatch)(Home); 68 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 8 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 9 | } 10 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import loadable from 'react-dynamic-loadable'; 3 | // import './index.css'; 4 | import { store } from "./../store" 5 | import About from "./about" 6 | import Home from "./home" 7 | import Notmatch from "./notmatch" 8 | import Repos from "./repos" 9 | import ReposDetail from "./repos/detail" 10 | 11 | // wrapper of dynamic 12 | const dynamicWrapper = (models: string[], component: () => Promise) => loadable({ 13 | component, 14 | LoadingComponent: () =>
...LOADING...
, 15 | models: () => models.map((m) => { 16 | return import(`../models/${m}.js`).then((md) => { 17 | const model = md.default || md; 18 | const stored = store(); 19 | if (stored && stored.model) { 20 | stored.addModel({ name: m, ...model }); 21 | } 22 | }); 23 | }), 24 | }); 25 | 26 | export const getRouterData = () => { 27 | let conf: Record = { 28 | '/': { 29 | name: 'page-home', 30 | // element: 31 | element: dynamicWrapper([], () => import(/* webpackChunkName: 'page-home' */ './home')), 32 | }, 33 | '/about': { 34 | name: 'page-about', 35 | element: , 36 | }, 37 | '/repos': { 38 | name: 'page-repos', 39 | element: , 40 | load: Repos.getInitialProps 41 | }, 42 | '/repos/detail/:id': { 43 | name: 'page-detail', 44 | element: , 45 | load: ReposDetail.getInitialProps 46 | }, 47 | '*': { 48 | name: 'page-not-match', 49 | element: , 50 | }, 51 | }; 52 | const list = Object.keys(conf).map((path) => { 53 | return { ...conf[path], path }; 54 | }); 55 | return list; 56 | }; 57 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/notmatch/index.module.less: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | margin: 50px auto 40px auto; 3 | width: 600px; 4 | text-align: center; 5 | color: rgba(0, 0, 0, 0.5); 6 | h1 { 7 | width: 800px; 8 | position: relative; 9 | left: -100px; 10 | letter-spacing: -1px; 11 | line-height: 60px; 12 | font-size: 60px; 13 | font-weight: 100; 14 | margin: 0px 0 50px 0; 15 | text-shadow: 0 1px 0 #fff; 16 | } 17 | } 18 | 19 | .title { 20 | color: rgba(0, 0, 0, 0.5); 21 | margin: 20px 0; 22 | line-height: 1.6; 23 | font-weight: bold; 24 | font-size: 18px; 25 | } 26 | 27 | .btn { 28 | padding: 20px 0; 29 | a { 30 | min-width: 80px; 31 | background-color: #3c6eb9; 32 | text-align: center; 33 | color: #fff; 34 | cursor: pointer; 35 | font-size: 16px; 36 | padding: 6px 10px; 37 | border: none; 38 | border-radius: 2px; 39 | text-decoration: none; 40 | &:hover { 41 | background-color: #154ea2; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/notmatch/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Helmet from 'react-helmet'; 4 | import styles from './index.module.less'; 5 | 6 | export default function NotMatch() { 7 | return ( 8 |
9 | 10 | Page not found 11 | 12 |

404

13 |
14 | Page not found 15 |
16 |
17 | This is not the web page you are looking for 18 |
19 |
20 | Back Home 21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/repos/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kktjs/ssr/e915732fed0e15ede971af4e75e1af1283e6dba8/example/react-router-rematch-old/src/routes/repos/avatar.png -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/repos/detail/index.css: -------------------------------------------------------------------------------- 1 | 2 | .blue { 3 | color: blue; 4 | } -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/repos/detail/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Helmet } from 'react-helmet'; 3 | import { GetInitialProps } from "@kkt/react-ssr-enhanced" 4 | import Container from '../../../components/Container'; 5 | import './index.css'; 6 | 7 | const Details = (props: { whatever?: string }) => { 8 | return ( 9 | 10 | 11 | Repos Details 12 | 13 |
14 | {props.whatever} 15 | Repos 2 Details 16 | {/* {match.params.id} */} 17 |
18 |
19 | ); 20 | } 21 | Details.getInitialProps = ({ match }: GetInitialProps) => { 22 | return { whatever: `This params id: ${match.params.id}. ` }; 23 | } 24 | export default Details -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/repos/home.module.css: -------------------------------------------------------------------------------- 1 | .home { 2 | text-align: center; 3 | } 4 | 5 | .logo { 6 | animation: logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .intro { 18 | font-size: large; 19 | } 20 | 21 | .resources { 22 | list-style: none; 23 | } 24 | 25 | .resources > li { 26 | display: inline-block; 27 | padding: 1rem; 28 | } 29 | 30 | 31 | @keyframes logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/repos/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Helmet } from 'react-helmet'; 4 | import styles from './home.module.css'; 5 | import avatar from './avatar.png'; 6 | import Container from '../../components/Container'; 7 | 8 | export default class Repos extends React.Component<{ whatever?: string }> { 9 | static async getInitialProps() { 10 | return { whatever: 'Repos stuff' }; 11 | } 12 | render() { 13 | return ( 14 | 15 | 16 | Repos SSS 17 | 18 |
19 | 20 | {this.props.whatever} 21 |
    22 |
  • 23 | About 24 |
  • 25 |
  • 26 | Docs 27 |
  • 28 |
  • 29 | Issues 30 |
  • 31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/username/index.css: -------------------------------------------------------------------------------- 1 | 2 | .blue { 3 | color: blue; 4 | } -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/routes/username/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './index.css'; 3 | import Container from "../../components/Container" 4 | 5 | class User extends React.Component { 6 | render() { 7 | return ( 8 | 9 |
10 | This User Home 11 |
12 |
13 | ); 14 | } 15 | } 16 | 17 | export default User; 18 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/server.ts: -------------------------------------------------------------------------------- 1 | 2 | import http from 'http'; 3 | import app from './serverIndex'; 4 | 5 | const logs = console.log; // eslint-disable-line 6 | const server = http.createServer(app); 7 | let currentApp = app; 8 | 9 | const PORT = parseInt(process.env.PORT || "3000") + 1; 10 | // @ts-ignore 11 | server.listen(PORT, (error) => { 12 | if (error) { 13 | logs(error); 14 | } 15 | logs('🚀 started!', `PORT: http://localhost:${PORT}`); 16 | }); 17 | // @ts-ignore 18 | if (module.hot) { 19 | logs('✅ Server-side HMR Enabled!'); 20 | // @ts-ignore 21 | module.hot.accept('./serverIndex', () => { 22 | logs('🔁 HMR Reloading `./serverIndex`...'); 23 | server.removeListener('request', currentApp); 24 | const newApp = require('./serverIndex').default; // eslint-disable-line 25 | server.on('request', newApp); 26 | currentApp = newApp; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/serverIndex.tsx: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cookieParser from 'cookie-parser'; 3 | import proxy from 'http-proxy-middleware'; 4 | import { render } from '@kkt/react-ssr-enhanced'; 5 | import { getRouterData } from './routes'; 6 | import Path from 'path'; 7 | import FS from 'fs'; 8 | import { createStore } from './store'; 9 | // @ts-ignore 10 | const assetsMainifest = new Function(`return ${FS.readFileSync(`${OUTPUT_PUBLIC_PATH}/asset-client-manifest.json`, "utf-8")}`)() 11 | 12 | 13 | const appDirectory = FS.realpathSync(process.cwd()); 14 | const resolveApp = (relativePath: string) => Path.resolve(appDirectory, relativePath); 15 | 16 | const isDev = process.env.NODE_ENV === "development" 17 | 18 | // const target = `http://${process.env.HOST}:${process.env.PORT}` 19 | const target = `http://${process.env.HOST || "localhost"}:${process.env.PORT || 3000}` 20 | 21 | const routes = getRouterData(); 22 | const server = express(); 23 | 24 | server.disable('x-powered-by'); 25 | // API request to pass cookies 26 | // `getInitialProps` gets the required value via `req.cookies.token` 27 | server.use(cookieParser()); 28 | server.use(express.static(isDev ? target : resolveApp('dist'))); 29 | server.use('/api', proxy({ 30 | target, 31 | changeOrigin: true, 32 | })); 33 | server.get('/*', async (req, res) => { 34 | try { 35 | const store = await createStore(); 36 | const html = await render({ 37 | req, 38 | res, 39 | routes, 40 | assets: assetsMainifest, 41 | store, // This Redux 42 | }); 43 | res.send(html); 44 | } catch (error) { 45 | // eslint-disable-next-line 46 | console.log('html---server--error>>>>:', error); 47 | res.json(error); 48 | } 49 | }); 50 | 51 | export default server; 52 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { init, } from '@rematch/core'; 2 | import cookie from 'cookiejs'; 3 | import global from '../models/global'; 4 | import stores, { Store, RootModel, FullModel } from '../models'; 5 | 6 | let storeInit = init({ 7 | models: { 8 | global: global 9 | }, 10 | plugins: [ 11 | { 12 | createMiddleware: () => () => next => (action) => { 13 | if (typeof window !== 'undefined') { 14 | const token = cookie.get('token'); 15 | if (token) { 16 | cookie.set('token', token, 1); 17 | } 18 | } 19 | // do something here 20 | return next(action); 21 | }, 22 | }, 23 | ], 24 | }); 25 | export const store = (): Store => storeInit; 26 | export const createStore = async (initialState = stores.getState() || {}) => { 27 | const promises: Promise[] = []; 28 | Object.keys(initialState).forEach((name) => { 29 | if (name !== "loading") { 30 | promises.push(import(`../models/${name}`).then((md) => { 31 | const model = md.default || md; 32 | model.state = initialState[name] || {}; 33 | model.name = name; 34 | return storeInit.addModel({ ...model }); 35 | })); 36 | } 37 | }); 38 | await Promise.all(promises); 39 | return stores; 40 | }; 41 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.svg'; 4 | declare module '*.png'; 5 | declare module 'http-proxy-middleware' -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/utils/history.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | 3 | export default createBrowserHistory(); 4 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from 'axios'; 2 | 3 | let HOST = process.env.HOST; 4 | 5 | // Ensure host consistency. 6 | if (typeof window !== 'undefined' && window.location && window.location.hostname) { 7 | HOST = window.location.hostname; 8 | } 9 | 10 | axios.defaults.baseURL = `http://${HOST}:${process.env.PORT}`; 11 | 12 | // 拼接url参数 13 | function splitUrl(url: any, options: { [x: string]: any; }) { 14 | let urlNew = url; 15 | const paramsArray: string[] = []; 16 | Object.keys(options).forEach(key => paramsArray.push(`${key}=${options[key]}`)); 17 | if (Object.keys(options).length === 0) { 18 | return url; 19 | } 20 | if (/\?/.test(urlNew) === false) { 21 | urlNew = `${urlNew}?${paramsArray.join('&')}`; 22 | } else { 23 | urlNew += `&${paramsArray.join('&')}`; 24 | } 25 | return urlNew; 26 | } 27 | 28 | 29 | // Get the current location. 30 | // const location = history.location; 31 | const codeMessage: Record = { 32 | 200: '服务器成功返回请求的数据。', 33 | 201: '新建或修改数据成功。', 34 | 202: '一个请求已经进入后台排队(异步任务)。', 35 | 204: '删除数据成功。', 36 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 37 | 401: '用户没有权限(令牌、用户名、密码错误)。', 38 | 403: '用户得到授权,但是访问是被禁止的。', 39 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 40 | 406: '请求的格式不可得。', 41 | 410: '请求的资源被永久删除,且不会再得到的。', 42 | 422: '当创建一个对象时,发生一个验证错误。', 43 | 500: '服务器发生错误,请检查服务器。', 44 | 502: '网关错误。', 45 | 503: '服务不可用,服务器暂时过载或维护。', 46 | 504: '网关超时。', 47 | }; 48 | 49 | 50 | export interface RequestOptions extends AxiosRequestConfig { 51 | body?: any 52 | } 53 | 54 | /** 55 | * Requests a URL, returning a promise. 56 | * 57 | * @param {string} url The URL we want to request 58 | * @param {object} [options] The options we want to pass to "fetch" 59 | * @return {object} An object containing either "data" or "err" 60 | */ 61 | export default function request(url: string, options: RequestOptions = {}) { 62 | const method = options.method || 'GET'; 63 | const newOptions = { 64 | url, 65 | method, 66 | data: options.body, 67 | headers: { 68 | 'Content-Type': options.headers && options.headers.ContentType ? options.headers.ContentType : 'application/json; charset=utf-8', 69 | Accept: 'application/json', 70 | }, 71 | }; 72 | 73 | if (/(GET)/.test(method)) { 74 | newOptions.url = splitUrl(url, { ...options.body }); 75 | delete options.body; 76 | } 77 | 78 | return axios.request(newOptions) 79 | .then(response => response.data) 80 | .catch((err) => { 81 | const response = err.response || {}; 82 | if (response && response.status >= 200 && response.status < 300) { 83 | return response; 84 | } 85 | const errortext = codeMessage[response.status] || response.statusText; 86 | // The server renders the error output. 87 | if (typeof window === 'undefined') { 88 | console.log('request--> :', axios.defaults.baseURL); // eslint-disable-line 89 | console.log('request--> :', response.status, errortext); // eslint-disable-line 90 | console.log('request--> :', response.statusText); // eslint-disable-line 91 | } 92 | if (typeof window !== 'undefined') { 93 | if (response && response.data) { 94 | return response.data; 95 | } 96 | } 97 | const error = new Error(errortext); 98 | error.name = response.status; 99 | // @ts-ignore 100 | error.response = response; 101 | if (response.data) { 102 | // response.data 103 | return ''; 104 | } 105 | throw error; 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /example/react-router-rematch-old/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "esnext", 6 | "dom" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "declaration": true, 19 | "baseUrl": ".", 20 | "jsx": "react-jsx", 21 | "noFallthroughCasesInSwitch": true, 22 | "noEmit": true, 23 | "paths": { 24 | "@/*": [ 25 | "./src/*" 26 | ], 27 | } 28 | }, 29 | "include": [ 30 | "src/**/*", 31 | ".kktssrrc.ts" 32 | ] 33 | } -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "example/*", 4 | "core/", 5 | "packages/create-kkt-ssr", 6 | "packages/react-ssr-enhanced", 7 | "packages/kkt-plugin-less", 8 | "packages/kkt-plugin-ssr" 9 | ], 10 | "version": "3.1.13", 11 | "command": { 12 | "create": { 13 | "license": "MIT" 14 | }, 15 | "bootstrap": { 16 | "yarnClientArgs": [ 17 | "--no-yarn-lock", 18 | "--no-package-lock" 19 | ], 20 | "npmClientArgs": [ 21 | "--no-yarn-lock", 22 | "--no-package-lock" 23 | ], 24 | "forceLocal": true 25 | } 26 | }, 27 | "npmClient": "yarn", 28 | "useWorkspaces": true 29 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kkt/root", 3 | "private": true, 4 | "description": "A baseline for server side rendering for your React application.", 5 | "scripts": { 6 | "build": "npm run build:ssr && npm run build:enhanced && npm run build:less && npm run build:create-kkt-ssr", 7 | "-----build:ssr-----": "----------", 8 | "build:ssr": "lerna exec --scope @kkt/ssr -- npm run build", 9 | "watch:ssr": "lerna exec --scope @kkt/ssr -- npm run watch", 10 | "-----build:react-ssr-enhanced-----": "----------", 11 | "build:enhanced": "lerna exec --scope @kkt/react-ssr-enhanced -- npm run build", 12 | "watch:enhanced": "lerna exec --scope @kkt/react-ssr-enhanced -- npm run watch", 13 | "-----build:kkt-plugin-less-----": "----------", 14 | "build:less": "lerna exec --scope @kkt/plugin-less -- npm run build", 15 | "watch:less": "lerna exec --scope @kkt/plugin-less -- npm run watch", 16 | "-----build:kkt-plugin-ssr-----": "----------", 17 | "build:plugin-ssr": "lerna exec --scope @kkt/plugin-ssr -- npm run build", 18 | "watch:plugin-ssr": "lerna exec --scope @kkt/plugin-ssr -- npm run watch", 19 | "-----build:create-kkt-ssr-----": "----------", 20 | "build:create-kkt-ssr": "lerna exec --scope create-kkt-ssr -- npm run build", 21 | "watch:create-kkt-ssr": "lerna exec --scope create-kkt-ssr -- npm run watch", 22 | "-------------": "-------------------", 23 | "hoist": "lerna bootstrap --hoist", 24 | "bootstrap": "lerna bootstrap", 25 | "build:basic": "lerna exec --scope @examples/basic -- npm run build", 26 | "build:basic:ex": "lerna exec --scope @examples/basic -- npm run build:ex", 27 | "build:basic:plugin": "lerna exec --scope @examples/basic-plugins -- npm run build", 28 | "build:js": "node ./script/createjs", 29 | "build:zip": "node ./script/zip.js", 30 | "--------------": "-------------------", 31 | "version": "lerna version --exact --force-publish --no-push --no-git-tag-version", 32 | "clean": "lerna clean && npm run remove && rm -rf ./package-lock.json && rm -rf node_modules && npm run remove:c ", 33 | "remove": " lerna exec --scope @kkt/* --scope @example/* -- rm -rf package-lock.json", 34 | "remove:c": "rm -rf package-lock.json && npm run remove:yarn && npm run remove:lib && npm run remove:esm", 35 | "remove:yarn": "lerna exec --scope @kkt/* --scope @example/* -- rm -rf yarn.lock", 36 | "remove:lib": "lerna exec --scope @kkt/* -- rm -rf ./lib", 37 | "remove:esm": "lerna exec --scope @kkt/* -- rm -rf ./esm", 38 | "test": "tsbb test", 39 | "tsbb": "tsbb", 40 | "lerna": "lerna" 41 | }, 42 | "author": "Kenny Wong (https://github.com/jaywcjlove)", 43 | "license": "MIT", 44 | "husky": { 45 | "hooks": { 46 | "pre-commit": "lint-staged" 47 | } 48 | }, 49 | "lint-staged": { 50 | "*.{js,jsx,ts,tsx,less,md,json}": [ 51 | "prettier --write" 52 | ] 53 | }, 54 | "jest": { 55 | "testMatch": [ 56 | "/test/*.test.{js,ts}" 57 | ] 58 | }, 59 | "devDependencies": { 60 | "archiver": "5.3.0", 61 | "husky": "7.0.4", 62 | "lerna": "4.0.0", 63 | "lint-staged": "12.3.4", 64 | "prettier": "2.5.1", 65 | "tsbb": "~3.7.2" 66 | }, 67 | "workspaces": { 68 | "packages": [ 69 | "example/*", 70 | "core/", 71 | "packages/create-kkt-ssr", 72 | "packages/react-ssr-enhanced", 73 | "packages/kkt-plugin-less", 74 | "packages/kkt-plugin-ssr" 75 | ], 76 | "nohoist": [] 77 | }, 78 | "version": "0.0.0" 79 | } 80 | -------------------------------------------------------------------------------- /packages/create-kkt-ssr/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | package-lock.json 4 | .DS_Store 5 | .cache 6 | .vscode 7 | 8 | *.log 9 | *.bak 10 | *.tem 11 | *.temp 12 | #.swp 13 | *.*~ 14 | ~*.* 15 | -------------------------------------------------------------------------------- /packages/create-kkt-ssr/README.md: -------------------------------------------------------------------------------- 1 | Create KKT App 2 | --- 3 | 4 | Create universal React apps in one command. 5 | 6 | ```bash 7 | npx create-kkt-app my-app 8 | cd my-app 9 | npm start 10 | ``` 11 | 12 | You can also initialize a project from one of the examples. 13 | 14 | ```bash 15 | npx create-kkt-app --example react-router my-app 16 | cd my-app 17 | npm start 18 | ``` 19 | 20 | Created with yarn: 21 | 22 | ```bash 23 | yarn create kkt-app my-app 24 | ``` 25 | 26 | ### Command Help 27 | 28 | ```bash 29 | Usage: create-kkt-app [options] 30 | 31 | A baseline for server side rendering for your React application. 32 | 33 | Options: 34 | -v, --version output the version number 35 | -e, --example Example from https://github.com/kktjs/ssr/tree/master/example example-path (default: "basic") 36 | -h, --help output usage information 37 | 38 | Examples: 39 | 40 | $ create-kkt-app 41 | $ create-kkt-app my-app 42 | $ create-kkt-app -e react-router my-app 43 | ``` 44 | 45 | ## Contributors 46 | 47 | As always, thanks to our amazing contributors! 48 | 49 | 50 | 51 | 52 | 53 | Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors). 54 | 55 | ## License 56 | 57 | Licensed under the MIT License 58 | -------------------------------------------------------------------------------- /packages/create-kkt-ssr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-kkt-ssr", 3 | "version": "3.1.13", 4 | "description": "CLI tool to bootstrap KKT applications with no configuration", 5 | "main": "lib/cli.js", 6 | "bin": { 7 | "create-kkt-ssr": "lib/cli.js" 8 | }, 9 | "scripts": { 10 | "build": "tsbb build --disable-babel --file-names src/cli.ts", 11 | "watch": "tsbb watch --disable-babel --file-names src/cli.ts", 12 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,md,json}'", 13 | "coverage": "tsbb test --coverage --detectOpenHandles", 14 | "test": "tsbb test --detectOpenHandles" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/kktjs/ssr/tree/master/packages/create-kkt-ssr" 19 | }, 20 | "keywords": [ 21 | "react", 22 | "react-router", 23 | "redux", 24 | "rematch", 25 | "webpack", 26 | "express", 27 | "ssr", 28 | "next.js" 29 | ], 30 | "author": "Kenny Wong (https://github.com/jaywcjlove)", 31 | "license": "MIT", 32 | "devDependencies": { 33 | "prettier": "2.5.1", 34 | "pretty-quick": "3.1.3", 35 | "tsbb": "~3.7.2" 36 | }, 37 | "dependencies": { 38 | "create-kkt": "~3.0.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/create-kkt-ssr/src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { run } from './utils'; 4 | 5 | try { 6 | run(); 7 | } catch (error) { 8 | console.log(`\x1b[31m${error.message}\x1b[0m`); 9 | console.log(error); 10 | process.exit(1); 11 | } 12 | -------------------------------------------------------------------------------- /packages/create-kkt-ssr/src/utils.ts: -------------------------------------------------------------------------------- 1 | import minimist from 'minimist'; 2 | import { create } from 'create-kkt'; 3 | 4 | export async function run(): Promise { 5 | const argvs = minimist(process.argv.slice(2), { 6 | alias: { 7 | output: 'o', 8 | version: 'v', 9 | force: 'f', 10 | path: 'p', 11 | example: 'e', 12 | }, 13 | default: { 14 | path: 'https://kktjs.github.io/ssr/zip/', 15 | output: '.', 16 | force: false, 17 | example: 'basic', 18 | }, 19 | }); 20 | if (argvs.h || argvs.help) { 21 | console.log(helpCli); 22 | return; 23 | } 24 | const { version } = require('../package.json'); 25 | if (argvs.v || argvs.version) { 26 | console.log(`\n create-kkt-ssr v${version}\n`); 27 | return; 28 | } 29 | argvs.appName = argvs._[0]; 30 | argvs.example = argvs.e = String(argvs.example).toLocaleLowerCase(); 31 | await create(argvs, helpExample); 32 | } 33 | 34 | export const helpExample: string = `Example: 35 | 36 | \x1b[35myarn\x1b[0m create kkt-ssr \x1b[33mappName\x1b[0m 37 | \x1b[35mnpx\x1b[0m create-kkt-ssr \x1b[33mmy-app\x1b[0m 38 | \x1b[35mnpm\x1b[0m create kkt-ssr \x1b[33mmy-app\x1b[0m 39 | \x1b[35mnpm\x1b[0m create kkt-ssr \x1b[33mmy-app\x1b[0m -f 40 | \x1b[35mnpm\x1b[0m create kkt-ssr \x1b[33mmy-app\x1b[0m -p \x1b[34mhttps://kktjs.github.io/ssr/zip/\x1b[0m 41 | `; 42 | 43 | export const helpCli: string = ` 44 | Usage: create-kkt-ssr [options] [--help|h] 45 | 46 | Options: 47 | 48 | --version, -v Show version number 49 | --help, -h Displays help information. 50 | --output, -o Output directory. 51 | --example, -e Example from: \x1b[34mhttps://kktjs.github.io/ssr/zip/\x1b[0m, default: "basic" 52 | --path, -p Specify the download target git address. 53 | default: "\x1b[34mhttps://kktjs.github.io/ssr/zip/\x1b[0m" 54 | 55 | ${helpExample} 56 | 57 | Copyright 2022 58 | 59 | `; 60 | -------------------------------------------------------------------------------- /packages/create-kkt-ssr/test/cli.test.ts: -------------------------------------------------------------------------------- 1 | /** @jest-environment node */ 2 | import fs from 'fs-extra'; 3 | import path from 'path'; 4 | import pkg from '../package.json'; 5 | import { helpCli, run, helpExample } from '../src/utils'; 6 | 7 | it('help test case.', async () => { 8 | expect(typeof helpExample).toEqual('string'); 9 | expect(typeof helpCli).toEqual('string'); 10 | }); 11 | 12 | it('help test case.', async () => { 13 | const mockExit = jest.spyOn(console, 'log').mockImplementation(); 14 | process.argv = process.argv.slice(0, 2); 15 | process.argv.push('my-app3'); 16 | process.argv.push('--help'); 17 | await import('../src/cli'); 18 | expect(mockExit).toHaveBeenCalledWith(helpCli); 19 | mockExit.mockRestore(); 20 | mockExit.mockClear(); 21 | mockExit.mockReset(); 22 | }); 23 | 24 | it('version test case.', async () => { 25 | const mockExit = jest.spyOn(console, 'log').mockImplementation(); 26 | process.argv = process.argv.slice(0, 2); 27 | process.argv.push('my-app4'); 28 | process.argv.push('--version'); 29 | await run(); 30 | // @ts-ignore 31 | expect(mockExit).toHaveBeenCalledWith( 32 | `\n create-kkt-ssr v${pkg.version}\n`, 33 | ); 34 | mockExit.mockRestore(); 35 | mockExit.mockClear(); 36 | mockExit.mockReset(); 37 | }); 38 | 39 | it('create project. 1', async () => { 40 | console.log = jest.fn(); 41 | process.argv = process.argv.slice(0, 2); 42 | process.argv.push('my-app2'); 43 | process.argv.push('-f'); 44 | process.argv.push('--output'); 45 | process.argv.push('test'); 46 | await run(); 47 | expect(await fs.existsSync(path.resolve(__dirname, 'my-app2'))).toBeTruthy(); 48 | await fs.remove('test/my-app2'); 49 | }, 10000); 50 | -------------------------------------------------------------------------------- /packages/create-kkt-ssr/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "declaration": true, 6 | "target": "es2017", 7 | "noImplicitAny": true, 8 | "resolveJsonModule": true, 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "sourceMap": true, 13 | "strict": false, 14 | "skipLibCheck": true, 15 | "outDir": "lib", 16 | "baseUrl": "." 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/kkt-plugin-less/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | package-lock.json 4 | .DS_Store 5 | .cache 6 | .vscode 7 | 8 | *.log 9 | *.bak 10 | *.tem 11 | *.temp 12 | #.swp 13 | *.*~ 14 | ~*.* 15 | -------------------------------------------------------------------------------- /packages/kkt-plugin-less/README.md: -------------------------------------------------------------------------------- 1 | @kkt/plugin-less 2 | --- 3 | 4 | This package contains a plugin for using [Less](https://github.com/less/less.js) with [@kkt/ssr](https://github.com/kktjs/ssr). 5 | 6 | 7 | ## Usage in @kkt/ssr Projects 8 | 9 | ```bash 10 | npm add @kkt/plugin-less --dev 11 | ``` 12 | 13 | ### With the default options 14 | 15 | ```js 16 | // .kktrc.js 17 | module.exports = { 18 | plugins: [ 19 | require.resolve('@kkt/plugin-less'), 20 | ], 21 | }; 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/kkt-plugin-less/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kkt/plugin-less", 3 | "version": "3.1.13", 4 | "description": "Support less.", 5 | "main": "lib/index.js", 6 | "module": "esm/index.js", 7 | "types": "lib/index.d.ts", 8 | "scripts": { 9 | "watch": "tsbb watch", 10 | "build": "tsbb build" 11 | }, 12 | "keywords": [ 13 | "less", 14 | "react", 15 | "kkt", 16 | "ssr", 17 | "@kkt/ssr" 18 | ], 19 | "files": [ 20 | "lib", 21 | "esm", 22 | "src" 23 | ], 24 | "repository": "https://github.com/kktjs/ssr.git", 25 | "author": "Kenny Wong (https://github.com/jaywcjlove)", 26 | "bugs": { 27 | "mail": "wowohoo@qq.com", 28 | "url": "https://github.com/kktjs/ssr/issues" 29 | }, 30 | "license": "MIT", 31 | "dependencies": { 32 | "css-loader": "^6.7.1", 33 | "less": "^4.1.2", 34 | "less-loader": "^10.2.0", 35 | "style-loader": "^3.3.1" 36 | }, 37 | "devDependencies": { 38 | "kkt": "~7.1.5", 39 | "tsbb": "~3.7.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/kkt-plugin-less/src/index.ts: -------------------------------------------------------------------------------- 1 | import MiniCssExtractPlugin from "mini-css-extract-plugin" 2 | import { WebpackConfiguration } from 'kkt'; 3 | import { RuleSetRule } from "webpack" 4 | const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); 5 | 6 | export interface LessOptions { 7 | target: "node" | "web"; 8 | env: "development" | "production", 9 | paths: Record 10 | shouldUseSourceMap?: boolean 11 | } 12 | 13 | export default (conf: WebpackConfiguration, options: LessOptions): WebpackConfiguration => { 14 | let shouldUseSourceMap = options.shouldUseSourceMap || !!conf.devtool; 15 | 16 | const isWeb = options.target === "web" 17 | 18 | const IS_DEV = options.env === 'development'; 19 | const isEnvProduction = options.env === 'production'; 20 | const postcssLoader = { 21 | // Options for PostCSS as we reference these options twice 22 | // Adds vendor prefixing based on your specified browser support in 23 | // package.json 24 | loader: require.resolve('postcss-loader'), 25 | options: { 26 | postcssOptions: { 27 | // Necessary for external CSS imports to work 28 | // https://github.com/facebook/create-react-app/issues/2677 29 | ident: 'postcss', 30 | config: false, 31 | plugins: [ 32 | 'postcss-flexbugs-fixes', 33 | [ 34 | 'postcss-preset-env', 35 | { 36 | autoprefixer: { 37 | flexbox: 'no-2009', 38 | }, 39 | stage: 3, 40 | }, 41 | ], 42 | // Adds PostCSS Normalize as the reset css with default options, 43 | // so that it honors browserslist config in package.json 44 | // which in turn let's users customize the target behavior as per their needs. 45 | 'postcss-normalize', 46 | ] 47 | }, 48 | sourceMap: isEnvProduction ? shouldUseSourceMap : IS_DEV, 49 | }, 50 | }; 51 | 52 | // node 端 需要 exportOnlyLocals 值 53 | const modulesLocals: { exportOnlyLocals?: boolean } = {} 54 | if (!isWeb) { 55 | modulesLocals.exportOnlyLocals = true 56 | } 57 | 58 | const cssModuleOption = { 59 | importLoaders: 3, 60 | sourceMap: shouldUseSourceMap, 61 | modules: { 62 | mode: 'icss', 63 | ...modulesLocals, 64 | getLocalIdent: getCSSModuleLocalIdent, 65 | }, 66 | }; 67 | 68 | 69 | const getStyleLoader = (cssModuleOption: any) => { 70 | let rules: RuleSetRule[] = [ 71 | { 72 | loader: require.resolve('css-loader'), 73 | options: { 74 | ...cssModuleOption 75 | }, 76 | }, 77 | postcssLoader, 78 | // { 79 | // loader: require.resolve('resolve-url-loader'), 80 | // options: { 81 | // sourceMap: shouldUseSourceMap, 82 | // root: options.paths.appSrc, 83 | // }, 84 | // }, 85 | { 86 | loader: require.resolve('less-loader'), 87 | options: { 88 | sourceMap: true, 89 | } 90 | } 91 | ] 92 | if (isWeb && isEnvProduction) { 93 | rules.unshift({ 94 | loader: MiniCssExtractPlugin.loader, 95 | // css is located in `static/css`, use '../../' to locate index.html folder 96 | // in production `paths.publicUrlOrPath` can be a relative path 97 | options: options.paths.publicUrlOrPath.startsWith('.') 98 | ? { publicPath: '../../' } 99 | : {}, 100 | }) 101 | } 102 | if (isWeb && IS_DEV) { 103 | rules.unshift({ loader: require.resolve('style-loader') }) 104 | } 105 | return rules.filter(Boolean) 106 | } 107 | 108 | // "postcss" loader applies autoprefixer to our CSS. 109 | // "css" loader resolves paths in CSS and adds assets as dependencies. 110 | // "style" loader turns CSS into JS modules that inject