├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode └── launch.json ├── README.md ├── commitlint.config.js ├── config ├── env.js ├── getHttpsConfig.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── modules.js ├── paths.js ├── pnpTs.js ├── webpack.config.js └── webpackDevServer.config.js ├── jest.config.js ├── lerna.json ├── package.json ├── packages ├── example │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.tsx │ │ ├── global.d.ts │ │ ├── index.tsx │ │ ├── pages │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ └── routes │ │ │ └── index.tsx │ └── tsconfig.json ├── flower │ ├── .eslintcache │ ├── README.md │ ├── build │ │ ├── asset-manifest.json │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ ├── robots.txt │ │ └── static │ │ │ └── js │ │ │ ├── 2.132e0994.chunk.js │ │ │ ├── 2.132e0994.chunk.js.LICENSE.txt │ │ │ ├── 2.132e0994.chunk.js.map │ │ │ ├── 3.f25ec1fa.chunk.js │ │ │ ├── 3.f25ec1fa.chunk.js.map │ │ │ ├── main.b1e28d53.chunk.js │ │ │ ├── main.b1e28d53.chunk.js.map │ │ │ ├── runtime-main.b620f322.js │ │ │ └── runtime-main.b620f322.js.map │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.tsx │ │ ├── assets │ │ │ └── images │ │ │ │ └── 樱花.png │ │ ├── global.d.ts │ │ ├── index.tsx │ │ ├── pages │ │ │ └── index.tsx │ │ └── routes │ │ │ └── index.tsx │ └── tsconfig.json └── sakura │ ├── .eslintcache │ ├── README.md │ ├── build │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── static │ │ ├── css │ │ ├── 3.ef3b53dc.chunk.css │ │ └── 3.ef3b53dc.chunk.css.map │ │ └── js │ │ ├── 2.a82409d5.chunk.js │ │ ├── 2.a82409d5.chunk.js.LICENSE.txt │ │ ├── 2.a82409d5.chunk.js.map │ │ ├── 3.207f468b.chunk.js │ │ ├── 3.207f468b.chunk.js.LICENSE.txt │ │ ├── 3.207f468b.chunk.js.map │ │ ├── main.ef493f25.chunk.js │ │ ├── main.ef493f25.chunk.js.map │ │ ├── runtime-main.7f0bcf93.js │ │ └── runtime-main.7f0bcf93.js.map │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.tsx │ ├── assets │ │ └── images │ │ │ └── 樱花.png │ ├── global.d.ts │ ├── index.tsx │ ├── pages │ │ ├── index.module.scss │ │ └── index.tsx │ └── routes │ │ └── index.tsx │ └── tsconfig.json ├── scripts ├── bin.js ├── build.js ├── create.js ├── start.js └── test.js ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | indent_size = 2 7 | indent_style = space 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.(ts,tsx,js)] 14 | indent_style = space 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | scripts/* 3 | config/* 4 | deploy/* 5 | public/* 6 | *.css 7 | *.scss 8 | *.d.ts 9 | **/node_modules 10 | src/**/__tests__/**/*.(j|t)s?(x) 11 | src/**/?(*.)(spec|test).(j|t)s?(x) 12 | .vscode/* -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['react-app', 'plugin:prettier/recommended', 'plugin:jsx-a11y/recommended'], 4 | rules: { 5 | 'import/no-dynamic-require': 'warn', 6 | 'import/extensions': 'off', 7 | 'react/jsx-indent': 'warn', 8 | 'react-hooks/exhaustive-deps': 'warn', 9 | 10 | '@typescript-eslint/no-unused-vars': 'warn', // to error 11 | 'prettier/prettier': 'warn', // to error 12 | 'react-hooks/rules-of-hooks': 'error', 13 | 14 | 'no-useless-constructor': 'off', 15 | 'prefer-template': 'off', 16 | 17 | 'react/sort-comp': 'off', 18 | 'react/no-unescaped-entities': 'warn', 19 | }, 20 | parser: '@typescript-eslint/parser', 21 | plugins: ['react-hooks', '@typescript-eslint', 'jsx-a11y'], 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | /.pnp 6 | .pnp.js 7 | .vscode 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "arrowParens": "always", 4 | "singleQuote": true, 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/scripts/create.js" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinger 2 | 这是一个基于 monorepo 的 React 项目,包含了 React、TypeScript、Eslint、Prettier、Commitlint 等等工具。 3 | 项目使用 lerna 和 yarn workspace 来管理。 4 | ## monorepo 5 | monorepo 和 multirepo 是相对概念。 6 | 7 | 通常情况下,我们一个 git 仓库里面只会放一个项目,这种是 multirepo。而一个 git 仓库有多个项目,这个就叫 monorepo。 8 | 9 | 在开发中,我们可能经常遇到做一些分散的 H5 活动页的情况下,它们一个页面就是一个项目,并且是独立部署的。 10 | 11 | 如果我们对每个页面都新开一个 React 项目,基础配置一模一样,然后每个项目都放到一个新的 git 仓库,往往效率低下。 12 | 13 | 如果你有脚手架,那么创建新项目还比较简单。如果没有脚手架,就需要把之前项目配置拷贝过来做修改,影响效率。 14 | 15 | 这里使用了 lerna 和 yarn workspace,可以将公共依赖都安装到顶部,节省了资源开销。 16 | 17 | 所有项目通用一份 webpack 构建配置,不必针对每个项目都配置一下。 18 | 19 | ### yarn workspace 20 | yarn workspace 是 yarn 区别于 npm 的一个重要特征,你需要在 package.json 里面设置 private 为 true,然后设置 workspaces 属性。 21 | 22 | 这样你每次新建一个项目,安装依赖的时候,多个项目通用的 node_modules 都会装到项目根目录里面,无须为每个项目都安装一遍。 23 | ## 一些命令 24 | ### 创建一个子项目 25 | 这里编写了一个 NodeJS 脚本,可以把 packages/example 的文件拷贝到其他目录下面,实现生成新项目的效果。 26 | 27 | 运行 `yarn run create --p ` 28 | 29 | 如果你对 React 脚手架感兴趣,也可以使用我的 `generator-react-kit` 脚手架。使用命令很简单。 30 | ``` 31 | yarn global add generator-react-kit 32 | yo react-kit 33 | ``` 34 | ### 运行一个子项目 35 | `yarn workspace run start` 36 | ### 安装依赖到子项目 37 | `yarn workspace add xxx` 38 | ### 安装依赖到根目录 39 | `yarn add -W- D add xxx` 40 | ### 给所有项目安装 41 | `yarn workspaces add xxx` 42 | ### 构建 43 | 运行 `yarn workspace run build` 来构建。 44 | ## 项目配置 45 | ### commitlint 46 | 使用了 commitlint 来规范团队提交记录,只允许你使用 `feat\fix\chore\doc\refactor` 等等开头的提交。 47 | ### eslint + prettier 48 | 这里使用了 jsx-a11y 来对代码规范做校验,用 prettier 统一代码格式,保证每个人写出来的代码风格一致。 49 | ### husky + lint-staged 50 | 用 husky 设置 commit 的时候做 eslint 和 prettier 代码格式校验,在 push 之前进行一次构建,以便于检查出来一些报错。 51 | ### typescript 52 | 支持用 typescript 来编写 react 代码。 53 | ### webpack 54 | 使用 create-react-app 创建的项目,已经将 webpack 配置 eject 了出来,支持自己定制。 55 | ## 部署 56 | 现在都是对每个项目单独构建,配置 nginx 转发到不同的域名。目前已经有了两个项目: 57 | 1. [sakura](http://sakura.gyyin.top) 58 | 2. [flower](http://flower.gyyin.top) 59 | 60 | 另附教程: 61 | 1. [画一朵樱花](https://github.com/yinguangyao/blog/issues/48) 62 | 2. [落樱效果](https://github.com/yinguangyao/tinger/blob/master/packages/sakura/README.md) 63 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | [ 8 | 'feat', 9 | 'add', 10 | 'mod', 11 | 'del', 12 | 'merge', 13 | 'fix', 14 | 'style', 15 | 'test', 16 | 'revert', 17 | 'refactor', 18 | 'docs', 19 | 'chore', 20 | 'perf', 21 | 'ci', 22 | 'deploy', 23 | 'release', 24 | ], 25 | ], 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const paths = require('./paths') 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')] 9 | 10 | const NODE_ENV = process.env.NODE_ENV 11 | if (!NODE_ENV) { 12 | throw new Error('The NODE_ENV environment variable is required but was not specified.') 13 | } 14 | 15 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 16 | const dotenvFiles = [ 17 | `${paths.dotenv}.${NODE_ENV}.local`, 18 | // Don't include `.env.local` for `test` environment 19 | // since normally you expect tests to produce the same 20 | // results for everyone 21 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 22 | `${paths.dotenv}.${NODE_ENV}`, 23 | paths.dotenv, 24 | ].filter(Boolean) 25 | 26 | // Load environment variables from .env* files. Suppress warnings using silent 27 | // if this file is missing. dotenv will never modify any environment variables 28 | // that have already been set. Variable expansion is supported in .env files. 29 | // https://github.com/motdotla/dotenv 30 | // https://github.com/motdotla/dotenv-expand 31 | dotenvFiles.forEach((dotenvFile) => { 32 | if (fs.existsSync(dotenvFile)) { 33 | require('dotenv-expand')( 34 | require('dotenv').config({ 35 | path: dotenvFile, 36 | }) 37 | ) 38 | } 39 | }) 40 | 41 | // We support resolving modules according to `NODE_PATH`. 42 | // This lets you use absolute paths in imports inside large monorepos: 43 | // https://github.com/facebook/create-react-app/issues/253. 44 | // It works similar to `NODE_PATH` in Node itself: 45 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 46 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 47 | // Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. 48 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 49 | // We also resolve them to make sure all tools using them work consistently. 50 | const appDirectory = fs.realpathSync(process.cwd()) 51 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 52 | .split(path.delimiter) 53 | .filter((folder) => folder && !path.isAbsolute(folder)) 54 | .map((folder) => path.resolve(appDirectory, folder)) 55 | .join(path.delimiter) 56 | 57 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 58 | // injected into the application via DefinePlugin in webpack configuration. 59 | const REACT_APP = /^REACT_APP_/i 60 | 61 | function getClientEnvironment(publicUrl) { 62 | const raw = Object.keys(process.env) 63 | .filter((key) => REACT_APP.test(key)) 64 | .reduce( 65 | (env, key) => { 66 | env[key] = process.env[key] 67 | return env 68 | }, 69 | { 70 | // Useful for determining whether we’re running in production mode. 71 | // Most importantly, it switches React into the correct mode. 72 | NODE_ENV: process.env.NODE_ENV || 'development', 73 | // Useful for resolving the correct path to static assets in `public`. 74 | // For example, . 75 | // This should only be used as an escape hatch. Normally you would put 76 | // images into the `src` and `import` them in code to get their paths. 77 | PUBLIC_URL: publicUrl, 78 | // We support configuring the sockjs pathname during development. 79 | // These settings let a developer run multiple simultaneous projects. 80 | // They are used as the connection `hostname`, `pathname` and `port` 81 | // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` 82 | // and `sockPort` options in webpack-dev-server. 83 | WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, 84 | WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, 85 | WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, 86 | // Whether or not react-refresh is enabled. 87 | // react-refresh is not 100% stable at this time, 88 | // which is why it's disabled by default. 89 | // It is defined here so it is available in the webpackHotDevClient. 90 | FAST_REFRESH: process.env.FAST_REFRESH !== 'false', 91 | } 92 | ) 93 | // Stringify all values so we can feed into webpack DefinePlugin 94 | const stringified = { 95 | 'process.env': Object.keys(raw).reduce((env, key) => { 96 | env[key] = JSON.stringify(raw[key]) 97 | return env 98 | }, {}), 99 | } 100 | 101 | return { raw, stringified } 102 | } 103 | 104 | module.exports = getClientEnvironment 105 | -------------------------------------------------------------------------------- /config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const crypto = require('crypto') 6 | const chalk = require('react-dev-utils/chalk') 7 | const paths = require('./paths') 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')) 16 | } catch (err) { 17 | throw new Error(`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`) 18 | } 19 | 20 | try { 21 | // privateDecrypt will throw an error with an invalid key 22 | crypto.privateDecrypt(key, encrypted) 23 | } catch (err) { 24 | throw new Error(`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${err.message}`) 25 | } 26 | } 27 | 28 | // Read file and throw an error if it doesn't exist 29 | function readEnvFile(file, type) { 30 | if (!fs.existsSync(file)) { 31 | throw new Error( 32 | `You specified ${chalk.cyan(type)} in your env, but the file "${chalk.yellow( 33 | file 34 | )}" can't be found.` 35 | ) 36 | } 37 | return fs.readFileSync(file) 38 | } 39 | 40 | // Get the https config 41 | // Return cert files if provided in env, otherwise just true or false 42 | function getHttpsConfig() { 43 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env 44 | const isHttps = HTTPS === 'true' 45 | 46 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 47 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE) 48 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE) 49 | const config = { 50 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 51 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 52 | } 53 | 54 | validateKeyAndCerts({ ...config, keyFile, crtFile }) 55 | return config 56 | } 57 | return isHttps 58 | } 59 | 60 | module.exports = getHttpsConfig 61 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};' 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform' 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const camelcase = require('camelcase') 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)) 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }) 19 | const componentName = `Svg${pascalCaseFilename}` 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };` 36 | } 37 | 38 | return `module.exports = ${assetFilename};` 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const paths = require('./paths') 6 | const chalk = require('react-dev-utils/chalk') 7 | const resolve = require('resolve') 8 | 9 | /** 10 | * Get additional module paths based on the baseUrl of a compilerOptions object. 11 | * 12 | * @param {Object} options 13 | */ 14 | function getAdditionalModulePaths(options = {}) { 15 | const baseUrl = options.baseUrl 16 | 17 | if (!baseUrl) { 18 | return '' 19 | } 20 | 21 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl) 22 | 23 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 24 | // the default behavior. 25 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 26 | return null 27 | } 28 | 29 | // Allow the user set the `baseUrl` to `appSrc`. 30 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 31 | return [paths.appSrc] 32 | } 33 | 34 | // If the path is equal to the root directory we ignore it here. 35 | // We don't want to allow importing from the root directly as source files are 36 | // not transpiled outside of `src`. We do allow importing them with the 37 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with 38 | // an alias. 39 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 40 | return null 41 | } 42 | 43 | // Otherwise, throw an error. 44 | throw new Error( 45 | chalk.red.bold( 46 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 47 | ' Create React App does not support other values at this time.' 48 | ) 49 | ) 50 | } 51 | 52 | /** 53 | * Get webpack aliases based on the baseUrl of a compilerOptions object. 54 | * 55 | * @param {*} options 56 | */ 57 | function getWebpackAliases(options = {}) { 58 | const baseUrl = options.baseUrl 59 | 60 | if (!baseUrl) { 61 | return {} 62 | } 63 | 64 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl) 65 | 66 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 67 | return { 68 | src: paths.appSrc, 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Get jest aliases based on the baseUrl of a compilerOptions object. 75 | * 76 | * @param {*} options 77 | */ 78 | function getJestAliases(options = {}) { 79 | const baseUrl = options.baseUrl 80 | 81 | if (!baseUrl) { 82 | return {} 83 | } 84 | 85 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl) 86 | 87 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 88 | return { 89 | '^src/(.*)$': '/src/$1', 90 | } 91 | } 92 | } 93 | 94 | function getModules() { 95 | // Check if TypeScript is setup 96 | const hasTsConfig = fs.existsSync(paths.appTsConfig) 97 | const hasJsConfig = fs.existsSync(paths.appJsConfig) 98 | 99 | if (hasTsConfig && hasJsConfig) { 100 | throw new Error( 101 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 102 | ) 103 | } 104 | 105 | let config 106 | 107 | // If there's a tsconfig.json we assume it's a 108 | // TypeScript project and set up the config 109 | // based on tsconfig.json 110 | if (hasTsConfig) { 111 | const ts = require(resolve.sync('typescript', { 112 | basedir: paths.appNodeModules, 113 | })) 114 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config 115 | // Otherwise we'll check if there is jsconfig.json 116 | // for non TS projects. 117 | } else if (hasJsConfig) { 118 | config = require(paths.appJsConfig) 119 | } 120 | 121 | config = config || {} 122 | const options = config.compilerOptions || {} 123 | 124 | const additionalModulePaths = getAdditionalModulePaths(options) 125 | 126 | return { 127 | additionalModulePaths: additionalModulePaths, 128 | webpackAliases: getWebpackAliases(options), 129 | jestAliases: getJestAliases(options), 130 | hasTsConfig, 131 | } 132 | } 133 | 134 | module.exports = getModules() 135 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath') 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()) 10 | const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath) 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right -------------------------------------------------------------------------------- /packages/flower/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/flower/build/logo192.png -------------------------------------------------------------------------------- /packages/flower/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/flower/build/logo512.png -------------------------------------------------------------------------------- /packages/flower/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/flower/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/flower/build/static/js/2.132e0994.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** @license React v0.20.1 8 | * scheduler.production.min.js 9 | * 10 | * Copyright (c) Facebook, Inc. and its affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | 16 | /** @license React v16.13.1 17 | * react-is.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v17.0.1 26 | * react-dom.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v17.0.1 35 | * react-jsx-runtime.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** @license React v17.0.1 44 | * react.production.min.js 45 | * 46 | * Copyright (c) Facebook, Inc. and its affiliates. 47 | * 48 | * This source code is licensed under the MIT license found in the 49 | * LICENSE file in the root directory of this source tree. 50 | */ 51 | -------------------------------------------------------------------------------- /packages/flower/build/static/js/3.f25ec1fa.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpflower=this.webpackJsonpflower||[]).push([[3],{28:function(t,e,a){"use strict";function n(t,e){for(var a=0;a40?[{x:o,y:s},{x:l,y:u},{x:f,y:v}]:[{x:l,y:u}],a.beginPath();for(var y=0;y 40) {\n ary = [{\n x: ax0,\n y: ay0\n }, {\n x: ax1,\n y: ay1\n }, {\n x: ax2,\n y: ay2\n }];\n } else {\n ary = [{\n x: ax1,\n y: ay1\n }];\n }\n\n ctx.beginPath();\n for (let i = 0; i < ary.length; i++) {\n ctx.moveTo(cx, cy);\n ctx.lineTo(ary[i].x, ary[i].y);\n ctx.arc(ary[i].x, ary[i].y, 2, 0, 2 * Math.PI)\n }\n ctx.stroke();\n ctx.restore();\n }\n}\n\nexport default function Index () {\n const ref = createRef()\n useLayoutEffect(() => {\n const ctx = ref.current?.getContext('2d');\n const flower = new Flower(ctx!);\n flower.drawFlower();\n }, [])\n return \n}","export default function _classCallCheck(instance, Constructor) {\n if (!(instance instanceof Constructor)) {\n throw new TypeError(\"Cannot call a class as a function\");\n }\n}"],"sourceRoot":""} -------------------------------------------------------------------------------- /packages/flower/build/static/js/main.b1e28d53.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpflower=this.webpackJsonpflower||[]).push([[0],{27:function(e,n,t){"use strict";t.r(n);var c=t(6),r=t(0),o=t.n(r),a=t(15),s=t.n(a),j=t(11),i=t(1),b=[{path:"/",component:Object(r.lazy)((function(){return t.e(3).then(t.bind(null,28))}))}];function l(){return Object(c.jsx)(r.Suspense,{fallback:Object(c.jsx)("h1",{children:"loading..."}),children:Object(c.jsx)(i.c,{children:b.map((function(e,n){return Object(c.jsx)(i.a,{exact:!0,path:e.path,component:e.component},n)}))})})}var u=function(){return Object(c.jsx)(j.a,{basename:"",children:Object(c.jsx)(l,{})})};s.a.render(Object(c.jsx)(o.a.StrictMode,{children:Object(c.jsx)(u,{})}),document.getElementById("root"))}},[[27,1,2]]]); 2 | //# sourceMappingURL=main.b1e28d53.chunk.js.map -------------------------------------------------------------------------------- /packages/flower/build/static/js/main.b1e28d53.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["routes/index.tsx","App.tsx","index.tsx"],"names":["routes","path","component","lazy","Routes","fallback","map","route","key","exact","App","basename","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"mKAGMA,EAAuF,CAC3F,CACEC,KAAM,IACNC,UAAWC,gBAAK,kBAAM,kCAIX,SAASC,IACtB,OACE,cAAC,WAAD,CAAUC,SAAU,4CAApB,SACE,cAAC,IAAD,UACGL,EAAOM,KAAI,SAACC,EAAOC,GAClB,OAAO,cAAC,IAAD,CAAiBC,OAAO,EAAMR,KAAMM,EAAMN,KAAMC,UAAWK,EAAML,WAArDM,UCFdE,MARf,WACE,OACE,cAAC,IAAD,CAAQC,SAAU,GAAlB,SACE,cAACP,EAAD,OCJNQ,IAASC,OACP,cAAC,IAAMC,WAAP,UACE,cAAC,EAAD,MAEFC,SAASC,eAAe,W","file":"static/js/main.b1e28d53.chunk.js","sourcesContent":["import React, { Suspense, lazy, ComponentType } from 'react'\nimport { Route, Switch } from 'react-router-dom'\n\nconst routes: { path: string; component: React.LazyExoticComponent> }[] = [\n {\n path: '/',\n component: lazy(() => import('../pages'))\n },\n]\n\nexport default function Routes() {\n return (\n loading...}>\n \n {routes.map((route, key) => {\n return \n })}\n \n \n )\n}\n","import React from 'react'\nimport { BrowserRouter as Router } from 'react-router-dom'\n\nimport Routes from '~/routes'\n\nfunction App() {\n return (\n \n \n \n );\n}\n\nexport default App;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root')\n);\n\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /packages/flower/build/static/js/runtime-main.b620f322.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],s=0,p=[];s0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "jest": { 47 | "roots": [ 48 | "/src" 49 | ], 50 | "collectCoverageFrom": [ 51 | "src/**/*.{js,jsx,ts,tsx}", 52 | "!src/**/*.d.ts" 53 | ], 54 | "setupFiles": [ 55 | "react-app-polyfill/jsdom" 56 | ], 57 | "setupFilesAfterEnv": [ 58 | "/src/setupTests.js" 59 | ], 60 | "testMatch": [ 61 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 62 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}" 63 | ], 64 | "testEnvironment": "jsdom", 65 | "testRunner": "/Users/guangyaoyin/Desktop/project/generator-react-kit/generators/templates/node_modules/jest-circus/runner.js", 66 | "transform": { 67 | "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "/node_modules/babel-jest", 68 | "^.+\\.css$": "/config/jest/cssTransform.js", 69 | "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "/config/jest/fileTransform.js" 70 | }, 71 | "transformIgnorePatterns": [ 72 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$", 73 | "^.+\\.module\\.(css|sass|scss)$" 74 | ], 75 | "modulePaths": [], 76 | "moduleNameMapper": { 77 | "^react-native$": "react-native-web", 78 | "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy" 79 | }, 80 | "moduleFileExtensions": [ 81 | "web.js", 82 | "js", 83 | "web.ts", 84 | "ts", 85 | "web.tsx", 86 | "tsx", 87 | "json", 88 | "web.jsx", 89 | "jsx", 90 | "node" 91 | ], 92 | "watchPlugins": [ 93 | "jest-watch-typeahead/filename", 94 | "jest-watch-typeahead/testname" 95 | ], 96 | "resetMocks": true 97 | } 98 | } -------------------------------------------------------------------------------- /packages/flower/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/flower/public/favicon.ico -------------------------------------------------------------------------------- /packages/flower/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Sakura 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/flower/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/flower/public/logo192.png -------------------------------------------------------------------------------- /packages/flower/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/flower/public/logo512.png -------------------------------------------------------------------------------- /packages/flower/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/flower/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/flower/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router } from 'react-router-dom' 3 | 4 | import Routes from '~/routes' 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /packages/flower/src/assets/images/樱花.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/flower/src/assets/images/樱花.png -------------------------------------------------------------------------------- /packages/flower/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test' 8 | readonly PUBLIC_URL: string 9 | } 10 | } 11 | 12 | declare module '*.bmp' { 13 | const src: string 14 | export default src 15 | } 16 | 17 | declare module '*.gif' { 18 | const src: string 19 | export default src 20 | } 21 | 22 | declare module '*.jpg' { 23 | const src: string 24 | export default src 25 | } 26 | 27 | declare module '*.jpeg' { 28 | const src: string 29 | export default src 30 | } 31 | 32 | declare module '*.png' { 33 | const src: string 34 | export default src 35 | } 36 | 37 | declare module '*.webp' { 38 | const src: string 39 | export default src 40 | } 41 | 42 | declare module '*.svg' { 43 | import * as React from 'react' 44 | 45 | export const ReactComponent: React.FunctionComponent> 46 | 47 | const src: string 48 | export default src 49 | } 50 | 51 | declare module '*.module.css' { 52 | const classes: { readonly [key: string]: string } 53 | export default classes 54 | } 55 | 56 | declare module '*.module.scss' { 57 | const classes: { readonly [key: string]: string } 58 | export default classes 59 | } 60 | 61 | declare module '*.module.sass' { 62 | const classes: { readonly [key: string]: string } 63 | export default classes 64 | } 65 | -------------------------------------------------------------------------------- /packages/flower/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /packages/flower/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, createRef } from 'react' 2 | 3 | const w = document.documentElement.clientWidth; 4 | const h = document.documentElement.clientHeight; 5 | 6 | const rad = 180 / Math.PI; // 一弧度 7 | const part = 360 / 5; // 樱花有五瓣 8 | const r = 150; // 半径 9 | const color = "hsl(2,70%,90%)"; 10 | 11 | class Flower { 12 | r = r; 13 | color = color; 14 | cx = w / 2; 15 | cy = h / 2; 16 | ctx: CanvasRenderingContext2D 17 | constructor(ctx: CanvasRenderingContext2D) { 18 | this.ctx = ctx 19 | } 20 | drawFlower() { 21 | const { ctx, r, color} = this 22 | ctx.fillStyle = color 23 | const r1 = r * 1.3; 24 | for (let a = 0; a < 5; a++) { 25 | 26 | this.drawPetal(a, r1) 27 | this.drawAnthers(a, r1); 28 | } 29 | } 30 | drawPetal(a: number, R1: number) { 31 | const { ctx, cx, cy, r: R } = this 32 | ctx.strokeStyle = "#d9d9d9"; 33 | ctx.fillStyle = color; 34 | 35 | const x0 = cx + R * Math.cos((a * part) / rad); 36 | const y0 = cy + R * Math.sin((a * part) / rad); 37 | 38 | const x1 = cx + R1 * Math.cos((a * part + 2 * part / 6) / rad); 39 | const y1 = cy + R1 * Math.sin((a * part + 2 * part / 6) / rad); 40 | 41 | const x2 = cx + R * Math.cos((a * part + 3 * part / 6) / rad); 42 | const y2 = cy + R * Math.sin((a * part + 3 * part / 6) / rad); 43 | 44 | const x3 = cx + R1 * Math.cos((a * part + 4 * part / 6) / rad); 45 | const y3 = cy + R1 * Math.sin((a * part + 4 * part / 6) / rad); 46 | 47 | const x4 = cx + R * Math.cos((a * part + part) / rad); 48 | const y4 = cy + R * Math.sin((a * part + part) / rad); 49 | 50 | // petal 51 | ctx.beginPath(); 52 | ctx.moveTo(cx, cy); 53 | ctx.quadraticCurveTo(x0, y0, x1, y1); 54 | ctx.lineTo(x2, y2); 55 | ctx.lineTo(x3, y3); 56 | ctx.quadraticCurveTo(x4, y4, cx, cy); 57 | ctx.fill(); 58 | ctx.stroke(); 59 | } 60 | drawAnthers(a: number, R1: number) { 61 | const { ctx, cx, cy, r: R } = this 62 | ctx.save(); 63 | ctx.strokeStyle = "#fff"; 64 | 65 | const ax0 = cx + R / 3 * Math.cos((a * part + 2 * part / 6) / rad); 66 | const ay0 = cy + R / 3 * Math.sin((a * part + 2 * part / 6) / rad); 67 | const ax1 = cx + R / 2 * Math.cos((a * part + 3 * part / 6) / rad); 68 | const ay1 = cy + R / 2 * Math.sin((a * part + 3 * part / 6) / rad); 69 | const ax2 = cx + R / 3 * Math.cos((a * part + 4 * part / 6) / rad); 70 | const ay2 = cy + R / 3 * Math.sin((a * part + 4 * part / 6) / rad); 71 | let ary = [] 72 | // 如果半径大于40 73 | if (R > 40) { 74 | ary = [{ 75 | x: ax0, 76 | y: ay0 77 | }, { 78 | x: ax1, 79 | y: ay1 80 | }, { 81 | x: ax2, 82 | y: ay2 83 | }]; 84 | } else { 85 | ary = [{ 86 | x: ax1, 87 | y: ay1 88 | }]; 89 | } 90 | 91 | ctx.beginPath(); 92 | for (let i = 0; i < ary.length; i++) { 93 | ctx.moveTo(cx, cy); 94 | ctx.lineTo(ary[i].x, ary[i].y); 95 | ctx.arc(ary[i].x, ary[i].y, 2, 0, 2 * Math.PI) 96 | } 97 | ctx.stroke(); 98 | ctx.restore(); 99 | } 100 | } 101 | 102 | export default function Index () { 103 | const ref = createRef() 104 | useLayoutEffect(() => { 105 | const ctx = ref.current?.getContext('2d'); 106 | const flower = new Flower(ctx!); 107 | flower.drawFlower(); 108 | }, []) 109 | return 110 | } -------------------------------------------------------------------------------- /packages/flower/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy, ComponentType } from 'react' 2 | import { Route, Switch } from 'react-router-dom' 3 | 4 | const routes: { path: string; component: React.LazyExoticComponent> }[] = [ 5 | { 6 | path: '/', 7 | component: lazy(() => import('../pages')) 8 | }, 9 | ] 10 | 11 | export default function Routes() { 12 | return ( 13 | loading...}> 14 | 15 | {routes.map((route, key) => { 16 | return 17 | })} 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/flower/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | }, 6 | "include": [ 7 | "src/*", 8 | ], 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/sakura/.eslintcache: -------------------------------------------------------------------------------- 1 | [{"/Users/guangyaoyin/Desktop/project/tinger/packages/sakura/src/App.tsx":"1","/Users/guangyaoyin/Desktop/project/tinger/packages/sakura/src/pages/index.tsx":"2","/Users/guangyaoyin/Desktop/project/tinger/packages/sakura/src/routes/index.tsx":"3"},{"size":231,"mtime":1606753701558,"results":"4","hashOfConfig":"5"},{"size":1398,"mtime":1606757175526,"results":"6","hashOfConfig":"5"},{"size":570,"mtime":1606753701556,"results":"7","hashOfConfig":"5"},{"filePath":"8","messages":"9","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"y9el1m",{"filePath":"10","messages":"11","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"12","messages":"13","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/guangyaoyin/Desktop/project/tinger/packages/sakura/src/App.tsx",[],"/Users/guangyaoyin/Desktop/project/tinger/packages/sakura/src/pages/index.tsx",[],"/Users/guangyaoyin/Desktop/project/tinger/packages/sakura/src/routes/index.tsx",[]] -------------------------------------------------------------------------------- /packages/sakura/README.md: -------------------------------------------------------------------------------- 1 | # 落樱效果 2 | 这是一个可爱的粉红色的樱花效果,项目是用 React 来写的。 3 | ## 制作流程 4 | ### 先画一朵樱花 5 | 第一步就是先画一朵樱花瓣,我们目标效果就是 `/assets/image/樱花.png` 那样的效果。 6 | 1. 首先画一个正方形的 div。 7 | ``` 8 |
9 | ``` 10 | 2. 设置这个正方形的 `border-radius`,左上和右下都设置为0,左下和右上都设置为 100%,这样得到一个类椭圆形。 11 | ``` 12 | .sakura { 13 | border-radius: 0 100%; // 100%是相当于宽度的 14 | } 15 | ``` 16 | 3. 再画一个一样的图形(两者是覆盖的),但是这个图形相对于右下角绕着Z轴旋转15度,就可以形成一个花瓣。 17 | ``` 18 | .sakura { 19 | position: relative; 20 | width: 20px; 21 | height: 20px; 22 | border-radius: 0% 100%; 23 | background: #fcc; 24 | 25 | &::before { 26 | content: ''; 27 | position: absolute; 28 | width: 100%; 29 | height: 100%; 30 | background: #fcc; 31 | border-radius: 0% 100%; 32 | transform-origin: 100% 100%; // 相对于右下角 33 | transform: rotateZ(15deg); 34 | } 35 | } 36 | ``` 37 | ### 花瓣掉落 38 | 接着要实现花瓣掉落效果,其实这个动画比较容易实现。通过 translateY 就可以将花瓣从顶部移动到底部。 39 | ``` 40 | @keyframes drop { 41 | 0% { 42 | transform: translateY(0); 43 | } 44 | 45 | 100% { 46 | transform: translateY(1200px); 47 | } 48 | } 49 | ``` 50 | 花瓣只从上往下掉也挺无聊的,我们再给她加上旋转效果。让它沿着 Y轴和Z轴旋转360度。 51 | ``` 52 | .rotate { 53 | animation: rotateY 2s linear infinite; 54 | } 55 | @keyframes rotateY { 56 | 0% { 57 | transform: rotateZ(0) rotateY(0deg); 58 | } 59 | 60 | 100% { 61 | transform: rotateZ(0) rotateY(360deg); 62 | } 63 | } 64 | 65 | .reverse { 66 | animation: rotateZ 10s linear infinite reverse; 67 | } 68 | 69 | @keyframes rotateZ { 70 | 0% { 71 | transform: rotateZ(0deg); 72 | } 73 | 74 | 100% { 75 | transform: rotateZ(360deg); 76 | } 77 | } 78 | ``` 79 | 目前我们的 html 结构是这样的: 80 | ``` 81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | ``` 90 | ### 花瓣飘动 91 | 只是掉落的效果也过于单调,为什么不能随风飘动呢?不如给花瓣加个移动效果。 92 | 加上一个从左移动到右边的动画效果。 93 | ``` 94 | .slide { 95 | position: absolute; 96 | top: 0; 97 | left: 0; 98 | animation: slide 8s linear infinite; 99 | } 100 | @keyframes slide { 101 | 0% { 102 | transform: translateX(-2000px); 103 | } 104 | 105 | 100% { 106 | transform: translateX(2000px); 107 | } 108 | } 109 | ``` 110 | 现在各种特效都已经有了,但只有一个花瓣,是不是太枯燥了点儿?那么给它随机生成200个吧。 111 | 在 React 里面用 map 来生成200个花瓣。 112 | ``` 113 | [...Array(200)].map((item, i) => { 114 | return
115 | } 116 | ``` 117 | 但现在这200个花瓣都重叠在一起了,我想要它们满足下面这几点: 118 | 1. 大小应该是随机的,一样大就缺少美感 119 | 2. 出生位置不一样,主要是 left 值不一样,不然都叠在了左边 120 | 3. 掉落速度不一样,不然我就只能看到一条横线 121 | 122 | 实现起来也很简单,sass本来就支持对css子元素遍历,那么我们用子元素选择器(nth-child)给它一个缩放。 123 | ``` 124 | @for $i from 1 through 200 { 125 | &:nth-child(#{$i}) { 126 | .size { 127 | transform: scale(random(2) + 1); 128 | } 129 | } 130 | } 131 | ``` 132 | 这样很好,200个花瓣大小已经不一样了,接着就随机设置 left 和掉落速度。 133 | ``` 134 | @for $i from 1 through 200 { 135 | &:nth-child(#{$i}) { 136 | 137 | // 设置 left 138 | left: random(100) + 0%; 139 | animation-delay: random(9999) * -1ms; 140 | animation-duration: random(8) + 5s; 141 | 142 | // 设置移动速度 143 | .slide { 144 | animation-delay: random(9999) * -1ms; 145 | animation-duration: random(10) + 5s; 146 | } 147 | // 设置缩放 148 | .size { 149 | transform: scale(random(2) + 1); 150 | } 151 | // 设置旋转速度 152 | .rotate { 153 | animation-delay: random(9999) * -1ms; 154 | animation-duration: random(4) + 1s; 155 | } 156 | } 157 | } 158 | ``` 159 | 这样我们就已经实现了很不错的樱花效果。 160 | ### 3D 视图 161 | 现在这样已经很完美了,但我还想实现一个花瓣由远及近的效果,看着更加真实生动。 162 | 这样,思考一下,不如对200个花瓣的容器绕着Y轴旋转,这样比起绕X轴旋转更加有风吹的效果。 163 | ``` 164 | @keyframes cameraY { 165 | 0% { 166 | transform: rotateY(0deg); 167 | } 168 | 169 | 100% { 170 | transform: rotateY(360deg); 171 | } 172 | } 173 | ``` 174 | 最终,我们实现了一个樱花随风掉落的效果。 -------------------------------------------------------------------------------- /packages/sakura/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.js": "/static/js/main.ef493f25.chunk.js", 4 | "main.js.map": "/static/js/main.ef493f25.chunk.js.map", 5 | "runtime-main.js": "/static/js/runtime-main.7f0bcf93.js", 6 | "runtime-main.js.map": "/static/js/runtime-main.7f0bcf93.js.map", 7 | "static/js/2.a82409d5.chunk.js": "/static/js/2.a82409d5.chunk.js", 8 | "static/js/2.a82409d5.chunk.js.map": "/static/js/2.a82409d5.chunk.js.map", 9 | "static/css/3.ef3b53dc.chunk.css": "/static/css/3.ef3b53dc.chunk.css", 10 | "static/js/3.207f468b.chunk.js": "/static/js/3.207f468b.chunk.js", 11 | "static/js/3.207f468b.chunk.js.map": "/static/js/3.207f468b.chunk.js.map", 12 | "index.html": "/index.html", 13 | "static/css/3.ef3b53dc.chunk.css.map": "/static/css/3.ef3b53dc.chunk.css.map", 14 | "static/js/2.a82409d5.chunk.js.LICENSE.txt": "/static/js/2.a82409d5.chunk.js.LICENSE.txt", 15 | "static/js/3.207f468b.chunk.js.LICENSE.txt": "/static/js/3.207f468b.chunk.js.LICENSE.txt" 16 | }, 17 | "entrypoints": [ 18 | "static/js/runtime-main.7f0bcf93.js", 19 | "static/js/2.a82409d5.chunk.js", 20 | "static/js/main.ef493f25.chunk.js" 21 | ] 22 | } -------------------------------------------------------------------------------- /packages/sakura/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/sakura/build/favicon.ico -------------------------------------------------------------------------------- /packages/sakura/build/index.html: -------------------------------------------------------------------------------- 1 | sakura
-------------------------------------------------------------------------------- /packages/sakura/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/sakura/build/logo192.png -------------------------------------------------------------------------------- /packages/sakura/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/sakura/build/logo512.png -------------------------------------------------------------------------------- /packages/sakura/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/sakura/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/sakura/build/static/js/2.a82409d5.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** @license React v0.20.1 8 | * scheduler.production.min.js 9 | * 10 | * Copyright (c) Facebook, Inc. and its affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | 16 | /** @license React v16.13.1 17 | * react-is.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v17.0.1 26 | * react-dom.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v17.0.1 35 | * react-jsx-runtime.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** @license React v17.0.1 44 | * react.production.min.js 45 | * 46 | * Copyright (c) Facebook, Inc. and its affiliates. 47 | * 48 | * This source code is licensed under the MIT license found in the 49 | * LICENSE file in the root directory of this source tree. 50 | */ 51 | -------------------------------------------------------------------------------- /packages/sakura/build/static/js/3.207f468b.chunk.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see 3.207f468b.chunk.js.LICENSE.txt */ 2 | (this.webpackJsonpsakura=this.webpackJsonpsakura||[]).push([[3],{28:function(e,a,r){var t;!function(){"use strict";var r={}.hasOwnProperty;function s(){for(var e=[],a=0;ae.length)&&(a=e.length);for(var r=0,t=new Array(a);r arr.length) len = arr.length;\n\n for (var i = 0, arr2 = new Array(len); i < len; i++) {\n arr2[i] = arr[i];\n }\n\n return arr2;\n}","import arrayWithoutHoles from \"@babel/runtime/helpers/esm/arrayWithoutHoles\";\nimport iterableToArray from \"@babel/runtime/helpers/esm/iterableToArray\";\nimport unsupportedIterableToArray from \"@babel/runtime/helpers/esm/unsupportedIterableToArray\";\nimport nonIterableSpread from \"@babel/runtime/helpers/esm/nonIterableSpread\";\nexport default function _toConsumableArray(arr) {\n return arrayWithoutHoles(arr) || iterableToArray(arr) || unsupportedIterableToArray(arr) || nonIterableSpread();\n}","import arrayLikeToArray from \"@babel/runtime/helpers/esm/arrayLikeToArray\";\nexport default function _arrayWithoutHoles(arr) {\n if (Array.isArray(arr)) return arrayLikeToArray(arr);\n}","export default function _iterableToArray(iter) {\n if (typeof Symbol !== \"undefined\" && Symbol.iterator in Object(iter)) return Array.from(iter);\n}","import arrayLikeToArray from \"@babel/runtime/helpers/esm/arrayLikeToArray\";\nexport default function _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\n}","export default function _nonIterableSpread() {\n throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}","import React from 'react';\nimport classnames from 'classnames'\n\nimport style from './index.module.scss'\n\nexport default function Index() {\n return (\n <>\n

sakura

\n
\n
\n
\n {\n [...Array(200)].map((item, i) => {\n return (\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n )\n })\n }\n
\n
\n
\n \n )\n}"],"sourceRoot":""} -------------------------------------------------------------------------------- /packages/sakura/build/static/js/main.ef493f25.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpsakura=this.webpackJsonpsakura||[]).push([[0],{27:function(e,n,t){"use strict";t.r(n);var c=t(6),a=t(0),r=t.n(a),s=t(15),o=t.n(s),j=t(11),i=t(1),u=[{path:"/",component:Object(a.lazy)((function(){return t.e(3).then(t.bind(null,30))}))}];function b(){return Object(c.jsx)(a.Suspense,{fallback:Object(c.jsx)("h1",{children:"loading..."}),children:Object(c.jsx)(i.c,{children:u.map((function(e,n){return Object(c.jsx)(i.a,{exact:!0,path:e.path,component:e.component},n)}))})})}var h=function(){return Object(c.jsx)(j.a,{basename:"",children:Object(c.jsx)(b,{})})};o.a.render(Object(c.jsx)(r.a.StrictMode,{children:Object(c.jsx)(h,{})}),document.getElementById("root"))}},[[27,1,2]]]); 2 | //# sourceMappingURL=main.ef493f25.chunk.js.map -------------------------------------------------------------------------------- /packages/sakura/build/static/js/main.ef493f25.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["routes/index.tsx","App.tsx","index.tsx"],"names":["routes","path","component","lazy","Routes","fallback","map","route","key","exact","App","basename","ReactDOM","render","StrictMode","document","getElementById"],"mappings":"mKAGMA,EAAuF,CAC3F,CACEC,KAAM,IACNC,UAAWC,gBAAK,kBAAM,kCAIX,SAASC,IACtB,OACE,cAAC,WAAD,CAAUC,SAAU,4CAApB,SACE,cAAC,IAAD,UACGL,EAAOM,KAAI,SAACC,EAAOC,GAClB,OAAO,cAAC,IAAD,CAAiBC,OAAO,EAAMR,KAAMM,EAAMN,KAAMC,UAAWK,EAAML,WAArDM,UCFdE,MARf,WACE,OACE,cAAC,IAAD,CAAQC,SAAU,GAAlB,SACE,cAACP,EAAD,OCJNQ,IAASC,OACP,cAAC,IAAMC,WAAP,UACE,cAAC,EAAD,MAEFC,SAASC,eAAe,W","file":"static/js/main.ef493f25.chunk.js","sourcesContent":["import React, { Suspense, lazy, ComponentType } from 'react'\nimport { Route, Switch } from 'react-router-dom'\n\nconst routes: { path: string; component: React.LazyExoticComponent> }[] = [\n {\n path: '/',\n component: lazy(() => import('../pages'))\n },\n]\n\nexport default function Routes() {\n return (\n loading...}>\n \n {routes.map((route, key) => {\n return \n })}\n \n \n )\n}\n","import React from 'react'\nimport { BrowserRouter as Router } from 'react-router-dom'\n\nimport Routes from '~/routes'\n\nfunction App() {\n return (\n \n \n \n );\n}\n\nexport default App;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport App from './App';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root')\n);\n\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /packages/sakura/build/static/js/runtime-main.7f0bcf93.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(t){for(var n,o,i=t[0],c=t[1],l=t[2],s=0,p=[];s0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "jest": { 47 | "roots": [ 48 | "/src" 49 | ], 50 | "collectCoverageFrom": [ 51 | "src/**/*.{js,jsx,ts,tsx}", 52 | "!src/**/*.d.ts" 53 | ], 54 | "setupFiles": [ 55 | "react-app-polyfill/jsdom" 56 | ], 57 | "setupFilesAfterEnv": [ 58 | "/src/setupTests.js" 59 | ], 60 | "testMatch": [ 61 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 62 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}" 63 | ], 64 | "testEnvironment": "jsdom", 65 | "testRunner": "/Users/guangyaoyin/Desktop/project/generator-react-kit/generators/templates/node_modules/jest-circus/runner.js", 66 | "transform": { 67 | "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "/node_modules/babel-jest", 68 | "^.+\\.css$": "/config/jest/cssTransform.js", 69 | "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "/config/jest/fileTransform.js" 70 | }, 71 | "transformIgnorePatterns": [ 72 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$", 73 | "^.+\\.module\\.(css|sass|scss)$" 74 | ], 75 | "modulePaths": [], 76 | "moduleNameMapper": { 77 | "^react-native$": "react-native-web", 78 | "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy" 79 | }, 80 | "moduleFileExtensions": [ 81 | "web.js", 82 | "js", 83 | "web.ts", 84 | "ts", 85 | "web.tsx", 86 | "tsx", 87 | "json", 88 | "web.jsx", 89 | "jsx", 90 | "node" 91 | ], 92 | "watchPlugins": [ 93 | "jest-watch-typeahead/filename", 94 | "jest-watch-typeahead/testname" 95 | ], 96 | "resetMocks": true 97 | } 98 | } -------------------------------------------------------------------------------- /packages/sakura/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/sakura/public/favicon.ico -------------------------------------------------------------------------------- /packages/sakura/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | sakura 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/sakura/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/sakura/public/logo192.png -------------------------------------------------------------------------------- /packages/sakura/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/sakura/public/logo512.png -------------------------------------------------------------------------------- /packages/sakura/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/sakura/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/sakura/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter as Router } from 'react-router-dom' 3 | 4 | import Routes from '~/routes' 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /packages/sakura/src/assets/images/樱花.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yinguangyao/tinger/54671b42823fd03ebb4ebf4686323cfb4ce1326e/packages/sakura/src/assets/images/樱花.png -------------------------------------------------------------------------------- /packages/sakura/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test' 8 | readonly PUBLIC_URL: string 9 | } 10 | } 11 | 12 | declare module '*.bmp' { 13 | const src: string 14 | export default src 15 | } 16 | 17 | declare module '*.gif' { 18 | const src: string 19 | export default src 20 | } 21 | 22 | declare module '*.jpg' { 23 | const src: string 24 | export default src 25 | } 26 | 27 | declare module '*.jpeg' { 28 | const src: string 29 | export default src 30 | } 31 | 32 | declare module '*.png' { 33 | const src: string 34 | export default src 35 | } 36 | 37 | declare module '*.webp' { 38 | const src: string 39 | export default src 40 | } 41 | 42 | declare module '*.svg' { 43 | import * as React from 'react' 44 | 45 | export const ReactComponent: React.FunctionComponent> 46 | 47 | const src: string 48 | export default src 49 | } 50 | 51 | declare module '*.module.css' { 52 | const classes: { readonly [key: string]: string } 53 | export default classes 54 | } 55 | 56 | declare module '*.module.scss' { 57 | const classes: { readonly [key: string]: string } 58 | export default classes 59 | } 60 | 61 | declare module '*.module.sass' { 62 | const classes: { readonly [key: string]: string } 63 | export default classes 64 | } 65 | -------------------------------------------------------------------------------- /packages/sakura/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /packages/sakura/src/pages/index.module.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: #fff; 3 | height: 100vh; 4 | overflow: hidden; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | perspective: 800px; 9 | font-family: 'Baloo Paaji 2', cursive; 10 | } 11 | 12 | .title { 13 | font-size: 6rem; 14 | color: #fcc; 15 | } 16 | 17 | div { 18 | transform-style: preserve-3d; 19 | top: 0; 20 | left: 0; 21 | } 22 | 23 | .camera { 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 100%; 29 | 30 | &.-y { 31 | animation: cameraY 30s linear infinite; 32 | } 33 | 34 | &.-x { 35 | transform: rotateX(10deg) translateY(-300px); 36 | } 37 | } 38 | 39 | .sakura { 40 | position: relative; 41 | width: 20px; 42 | height: 20px; 43 | border-radius: 0% 100%; 44 | background: #fcc; 45 | transform: rotateZ(45deg); 46 | 47 | &::before { 48 | content: ''; 49 | position: absolute; 50 | width: 100%; 51 | height: 100%; 52 | background: #fcc; 53 | border-radius: 0% 100% 0% 100% / 0% 100% 0% 100%; 54 | transform-origin: 100% 100%; 55 | transform: rotateZ(15deg); 56 | } 57 | } 58 | 59 | .slide { 60 | position: absolute; 61 | top: 0; 62 | left: 0; 63 | animation: slide 8s linear infinite; 64 | } 65 | 66 | .drop { 67 | position: absolute; 68 | top: 0; 69 | left: 0; 70 | animation: drop 4s linear infinite; 71 | 72 | @for $i from 1 through 200 { 73 | &:nth-child(#{$i}) { 74 | left: random(100) + 0%; 75 | animation-delay: random(9999) * -1ms; 76 | animation-duration: random(8) + 5s; 77 | 78 | .slide { 79 | animation-delay: random(9999) * -1ms; 80 | animation-duration: random(10) + 5s; 81 | } 82 | 83 | .size { 84 | transform: scale(random(2) + 1); 85 | // transform: translateZ(random(500) * -1px); 86 | } 87 | 88 | .rotate { 89 | animation-delay: random(9999) * -1ms; 90 | animation-duration: random(4) + 1s; 91 | } 92 | 93 | $moveDelay: random(9999) * -1ms; 94 | 95 | .move, .reverse { 96 | animation-delay: $moveDelay; 97 | } 98 | 99 | .z { 100 | transform: translateZ(random(2000) - 1000px); 101 | } 102 | 103 | // .sakura { 104 | // $color: rgb(random(55) + 200, random(55) + 200, random(55) + 200); 105 | // background: $color; 106 | 107 | // &::before { 108 | // background: $color; 109 | // } 110 | // } 111 | } 112 | } 113 | 114 | @for $i from 1 through 100 { 115 | &:nth-child(#{$i}) { 116 | .slide { 117 | animation: slide 8s linear infinite reverse; 118 | animation-delay: random(9999) * -1ms; 119 | animation-duration: random(10) + 10s; 120 | } 121 | 122 | .sakura { 123 | transform: rotateZ(-45deg); 124 | } 125 | 126 | .size { 127 | transform: scale(random(1)); 128 | } 129 | } 130 | } 131 | } 132 | 133 | .move { 134 | animation: rotateZ 10s linear infinite; 135 | } 136 | 137 | .reverse { 138 | animation: rotateZ 10s linear infinite reverse; 139 | } 140 | 141 | .stagger { 142 | transform: translateY(150px); 143 | } 144 | 145 | .rotate { 146 | animation: rotateY 2s linear infinite; 147 | } 148 | 149 | @keyframes rotateZ { 150 | 0% { 151 | transform: rotateZ(0deg); 152 | } 153 | 154 | 100% { 155 | transform: rotateZ(360deg); 156 | } 157 | } 158 | 159 | @keyframes rotateY { 160 | 0% { 161 | transform: rotateZ(-45deg) rotateY(0deg); 162 | } 163 | 164 | 100% { 165 | transform: rotateZ(-45deg) rotateY(360deg); 166 | } 167 | } 168 | 169 | @keyframes cameraX { 170 | 0% { 171 | transform: rotateX(0deg); 172 | } 173 | 174 | 100% { 175 | transform: rotateX(360deg); 176 | } 177 | } 178 | 179 | @keyframes cameraY { 180 | 0% { 181 | transform: rotateY(0deg); 182 | } 183 | 184 | 100% { 185 | transform: rotateY(360deg); 186 | } 187 | } 188 | 189 | @keyframes cameraZ { 190 | 0% { 191 | transform: rotateZ(0deg); 192 | } 193 | 194 | 100% { 195 | transform: rotateZ(360deg); 196 | } 197 | } 198 | 199 | 200 | @keyframes slide { 201 | 0% { 202 | transform: translateX(-2000px); 203 | } 204 | 205 | 100% { 206 | transform: translateX(2000px); 207 | } 208 | } 209 | 210 | @keyframes drop { 211 | 0% { 212 | transform: translateY(-100px); 213 | } 214 | 215 | 100% { 216 | transform: translateY(1200px); 217 | } 218 | } -------------------------------------------------------------------------------- /packages/sakura/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames' 3 | 4 | import style from './index.module.scss' 5 | 6 | export default function Index() { 7 | return ( 8 | <> 9 |

sakura

10 |
11 |
12 |
13 | { 14 | [...Array(200)].map((item, i) => { 15 | return ( 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ) 35 | }) 36 | } 37 |
38 |
39 |
40 | 41 | ) 42 | } -------------------------------------------------------------------------------- /packages/sakura/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy, ComponentType } from 'react' 2 | import { Route, Switch } from 'react-router-dom' 3 | 4 | const routes: { path: string; component: React.LazyExoticComponent> }[] = [ 5 | { 6 | path: '/', 7 | component: lazy(() => import('../pages')) 8 | }, 9 | ] 10 | 11 | export default function Routes() { 12 | return ( 13 | loading...}> 14 | 15 | {routes.map((route, key) => { 16 | return 17 | })} 18 | 19 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /packages/sakura/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | }, 6 | "include": [ 7 | "src/*", 8 | ], 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /scripts/bin.js: -------------------------------------------------------------------------------- 1 | const yargs = require('yargs'); 2 | 3 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'production' 5 | process.env.NODE_ENV = 'production' 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', (err) => { 11 | throw err 12 | }) 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env') 16 | 17 | const path = require('path') 18 | const chalk = require('react-dev-utils/chalk') 19 | const fs = require('fs-extra') 20 | const bfj = require('bfj') 21 | const webpack = require('webpack') 22 | const configFactory = require('../config/webpack.config') 23 | const paths = require('../config/paths') 24 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles') 25 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages') 26 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions') 27 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter') 28 | const printBuildError = require('react-dev-utils/printBuildError') 29 | 30 | const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild 31 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild 32 | const useYarn = fs.existsSync(paths.yarnLockFile) 33 | 34 | // These sizes are pretty large. We'll warn for bundles exceeding them. 35 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024 36 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024 37 | 38 | const isInteractive = process.stdout.isTTY 39 | 40 | // Warn and crash if required files are missing 41 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 42 | process.exit(1) 43 | } 44 | 45 | const argv = process.argv.slice(2) 46 | const writeStatsJson = argv.indexOf('--stats') !== -1 47 | 48 | // Generate configuration 49 | const config = configFactory('production') 50 | 51 | // We require that you explicitly set browsers and do not fall back to 52 | // browserslist defaults. 53 | const { checkBrowsers } = require('react-dev-utils/browsersHelper') 54 | checkBrowsers(paths.appPath, isInteractive) 55 | .then(() => { 56 | // First, read the current file sizes in build directory. 57 | // This lets us display how much they changed later. 58 | return measureFileSizesBeforeBuild(paths.appBuild) 59 | }) 60 | .then((previousFileSizes) => { 61 | // Remove all content but keep the directory so that 62 | // if you're in it, you don't end up in Trash 63 | fs.emptyDirSync(paths.appBuild) 64 | // Merge with the public folder 65 | copyPublicFolder() 66 | // Start the webpack build 67 | return build(previousFileSizes) 68 | }) 69 | .then( 70 | ({ stats, previousFileSizes, warnings }) => { 71 | if (warnings.length) { 72 | console.log(chalk.yellow('Compiled with warnings.\n')) 73 | console.log(warnings.join('\n\n')) 74 | console.log( 75 | '\nSearch for the ' + 76 | chalk.underline(chalk.yellow('keywords')) + 77 | ' to learn more about each warning.' 78 | ) 79 | console.log( 80 | 'To ignore, add ' + chalk.cyan('// eslint-disable-next-line') + ' to the line before.\n' 81 | ) 82 | } else { 83 | console.log(chalk.green('Compiled successfully.\n')) 84 | } 85 | 86 | console.log('File sizes after gzip:\n') 87 | printFileSizesAfterBuild( 88 | stats, 89 | previousFileSizes, 90 | paths.appBuild, 91 | WARN_AFTER_BUNDLE_GZIP_SIZE, 92 | WARN_AFTER_CHUNK_GZIP_SIZE 93 | ) 94 | console.log() 95 | 96 | const appPackage = require(paths.appPackageJson) 97 | const publicUrl = paths.publicUrlOrPath 98 | const publicPath = config.output.publicPath 99 | const buildFolder = path.relative(process.cwd(), paths.appBuild) 100 | printHostingInstructions(appPackage, publicUrl, publicPath, buildFolder, useYarn) 101 | }, 102 | (err) => { 103 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true' 104 | if (tscCompileOnError) { 105 | console.log( 106 | chalk.yellow( 107 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n' 108 | ) 109 | ) 110 | printBuildError(err) 111 | } else { 112 | console.log(chalk.red('Failed to compile.\n')) 113 | printBuildError(err) 114 | process.exit(1) 115 | } 116 | } 117 | ) 118 | .catch((err) => { 119 | if (err && err.message) { 120 | console.log(err.message) 121 | } 122 | process.exit(1) 123 | }) 124 | 125 | // Create the production build and print the deployment instructions. 126 | function build(previousFileSizes) { 127 | console.log('Creating an optimized production build...') 128 | 129 | const compiler = webpack(config) 130 | return new Promise((resolve, reject) => { 131 | compiler.run((err, stats) => { 132 | let messages 133 | if (err) { 134 | if (!err.message) { 135 | return reject(err) 136 | } 137 | 138 | let errMessage = err.message 139 | 140 | // Add additional information for postcss errors 141 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { 142 | errMessage += '\nCompileError: Begins at CSS selector ' + err['postcssNode'].selector 143 | } 144 | 145 | messages = formatWebpackMessages({ 146 | errors: [errMessage], 147 | warnings: [], 148 | }) 149 | } else { 150 | messages = formatWebpackMessages(stats.toJson({ all: false, warnings: true, errors: true })) 151 | } 152 | if (messages.errors.length) { 153 | // Only keep the first error. Others are often indicative 154 | // of the same problem, but confuse the reader with noise. 155 | if (messages.errors.length > 1) { 156 | messages.errors.length = 1 157 | } 158 | return reject(new Error(messages.errors.join('\n\n'))) 159 | } 160 | if ( 161 | process.env.CI && 162 | (typeof process.env.CI !== 'string' || process.env.CI.toLowerCase() !== 'false') && 163 | messages.warnings.length 164 | ) { 165 | console.log( 166 | chalk.yellow( 167 | '\nTreating warnings as errors because process.env.CI = true.\n' + 168 | 'Most CI servers set it automatically.\n' 169 | ) 170 | ) 171 | return reject(new Error(messages.warnings.join('\n\n'))) 172 | } 173 | 174 | const resolveArgs = { 175 | stats, 176 | previousFileSizes, 177 | warnings: messages.warnings, 178 | } 179 | 180 | if (writeStatsJson) { 181 | return bfj 182 | .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) 183 | .then(() => resolve(resolveArgs)) 184 | .catch((error) => reject(new Error(error))) 185 | } 186 | 187 | return resolve(resolveArgs) 188 | }) 189 | }) 190 | } 191 | 192 | function copyPublicFolder() { 193 | fs.copySync(paths.appPublic, paths.appBuild, { 194 | dereference: true, 195 | filter: (file) => file !== paths.appHtml, 196 | }) 197 | } 198 | -------------------------------------------------------------------------------- /scripts/create.js: -------------------------------------------------------------------------------- 1 | const yargs = require('yargs') 2 | const fs = require('fs') 3 | 4 | const { p: project } = yargs.argv 5 | const dirname = process.cwd() 6 | 7 | // 拷贝文件夹到目标目录 8 | const copy = (source, target) => { 9 | console.log(`source=${source}|target=${target}`) 10 | try { 11 | const stats = fs.statSync(source) 12 | if (stats.isDirectory()) { 13 | fs.readdir(source, (err, files) => { 14 | if (err) { 15 | console.error('copy files error', err); 16 | process.exit(1) 17 | } 18 | fs.access(target, (err) => { 19 | if (err) { 20 | fs.mkdirSync(target) 21 | } 22 | files.forEach(file => { 23 | copy(`${source}/${file}`, `${target}/${file}`) 24 | }) 25 | }) 26 | }) 27 | } else { 28 | if (target.indexOf('package.json') > -1) { 29 | return; 30 | } 31 | fs.createReadStream(source).pipe(fs.createWriteStream(target)) 32 | } 33 | } catch (err) { 34 | console.error(`err.stack=${err.stack}|err.message=${err.message}`); 35 | process.exit(1) 36 | } 37 | } 38 | 39 | const template = `${dirname}/packages/example` 40 | const target = `${dirname}/packages/${project}` 41 | try { 42 | const stats = fs.statSync(target) 43 | // 检查目录是否存在 44 | if (stats.isDirectory()) { 45 | console.error(`Sub project ${project} has been existed!`) 46 | process.exit(1) 47 | } 48 | } catch (err) { 49 | console.error(`err.stack=${err.stack}|err.message=${err.message}`); 50 | } 51 | 52 | fs.mkdirSync(target) 53 | copy(template, target); 54 | // 替换 package 里面的字段 55 | const package = require(`${template}/package.json`); 56 | package.name = project; 57 | fs.writeFile( 58 | `${target}/package.json`, 59 | JSON.stringify(package, null, "\t"), 60 | (err) => { 61 | if (err) { 62 | console.error('write err', err); 63 | process.exit(1) 64 | } 65 | } 66 | ) -------------------------------------------------------------------------------- /scripts/start.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'development' 5 | process.env.NODE_ENV = 'development' 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', (err) => { 11 | throw err 12 | }) 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env') 16 | 17 | const fs = require('fs') 18 | const chalk = require('react-dev-utils/chalk') 19 | const webpack = require('webpack') 20 | const WebpackDevServer = require('webpack-dev-server') 21 | const clearConsole = require('react-dev-utils/clearConsole') 22 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles') 23 | const { 24 | choosePort, 25 | createCompiler, 26 | prepareProxy, 27 | prepareUrls, 28 | } = require('react-dev-utils/WebpackDevServerUtils') 29 | const openBrowser = require('react-dev-utils/openBrowser') 30 | const semver = require('semver') 31 | const paths = require('../config/paths') 32 | const configFactory = require('../config/webpack.config') 33 | const createDevServerConfig = require('../config/webpackDevServer.config') 34 | const getClientEnvironment = require('../config/env') 35 | const react = require(require.resolve('react', { paths: [paths.appPath] })) 36 | 37 | const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)) 38 | const useYarn = fs.existsSync(paths.yarnLockFile) 39 | const isInteractive = process.stdout.isTTY 40 | 41 | // Warn and crash if required files are missing 42 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 43 | process.exit(1) 44 | } 45 | 46 | // Tools like Cloud9 rely on this. 47 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000 48 | const HOST = process.env.HOST || '0.0.0.0' 49 | 50 | if (process.env.HOST) { 51 | console.log( 52 | chalk.cyan( 53 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 54 | chalk.bold(process.env.HOST) 55 | )}` 56 | ) 57 | ) 58 | console.log(`If this was unintentional, check that you haven't mistakenly set it in your shell.`) 59 | console.log(`Learn more here: ${chalk.yellow('https://cra.link/advanced-config')}`) 60 | console.log() 61 | } 62 | 63 | // We require that you explicitly set browsers and do not fall back to 64 | // browserslist defaults. 65 | const { checkBrowsers } = require('react-dev-utils/browsersHelper') 66 | checkBrowsers(paths.appPath, isInteractive) 67 | .then(() => { 68 | // We attempt to use the default port but if it is busy, we offer the user to 69 | // run on a different port. `choosePort()` Promise resolves to the next free port. 70 | return choosePort(HOST, DEFAULT_PORT) 71 | }) 72 | .then((port) => { 73 | if (port == null) { 74 | // We have not found a port. 75 | return 76 | } 77 | 78 | const config = configFactory('development') 79 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http' 80 | const appName = require(paths.appPackageJson).name 81 | 82 | const useTypeScript = fs.existsSync(paths.appTsConfig) 83 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true' 84 | const urls = prepareUrls(protocol, HOST, port, paths.publicUrlOrPath.slice(0, -1)) 85 | const devSocket = { 86 | warnings: (warnings) => devServer.sockWrite(devServer.sockets, 'warnings', warnings), 87 | errors: (errors) => devServer.sockWrite(devServer.sockets, 'errors', errors), 88 | } 89 | // Create a webpack compiler that is configured with custom messages. 90 | const compiler = createCompiler({ 91 | appName, 92 | config, 93 | devSocket, 94 | urls, 95 | useYarn, 96 | useTypeScript, 97 | tscCompileOnError, 98 | webpack, 99 | }) 100 | // Load proxy config 101 | const proxySetting = require(paths.appPackageJson).proxy 102 | const proxyConfig = prepareProxy(proxySetting, paths.appPublic, paths.publicUrlOrPath) 103 | // Serve webpack assets generated by the compiler over a web server. 104 | const serverConfig = createDevServerConfig(proxyConfig, urls.lanUrlForConfig) 105 | const devServer = new WebpackDevServer(compiler, serverConfig) 106 | // Launch WebpackDevServer. 107 | devServer.listen(port, HOST, (err) => { 108 | if (err) { 109 | return console.log(err) 110 | } 111 | if (isInteractive) { 112 | clearConsole() 113 | } 114 | 115 | if (env.raw.FAST_REFRESH && semver.lt(react.version, '16.10.0')) { 116 | console.log( 117 | chalk.yellow( 118 | `Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.` 119 | ) 120 | ) 121 | } 122 | 123 | console.log(chalk.cyan('Starting the development server...\n')) 124 | openBrowser(urls.localUrlForBrowser) 125 | }) 126 | 127 | ;['SIGINT', 'SIGTERM'].forEach(function (sig) { 128 | process.on(sig, function () { 129 | devServer.close() 130 | process.exit() 131 | }) 132 | }) 133 | 134 | if (process.env.CI !== 'true') { 135 | // Gracefully exit when stdin ends 136 | process.stdin.on('end', function () { 137 | devServer.close() 138 | process.exit() 139 | }) 140 | } 141 | }) 142 | .catch((err) => { 143 | if (err && err.message) { 144 | console.log(err.message) 145 | } 146 | process.exit(1) 147 | }) 148 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test' 5 | process.env.NODE_ENV = 'test' 6 | process.env.PUBLIC_URL = '' 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', (err) => { 12 | throw err 13 | }) 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env') 17 | 18 | const jest = require('jest') 19 | const execSync = require('child_process').execSync 20 | let argv = process.argv.slice(2) 21 | 22 | function isInGitRepository() { 23 | try { 24 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }) 25 | return true 26 | } catch (e) { 27 | return false 28 | } 29 | } 30 | 31 | function isInMercurialRepository() { 32 | try { 33 | execSync('hg --cwd . root', { stdio: 'ignore' }) 34 | return true 35 | } catch (e) { 36 | return false 37 | } 38 | } 39 | 40 | // Watch unless on CI or explicitly running all tests 41 | if ( 42 | !process.env.CI && 43 | argv.indexOf('--watchAll') === -1 && 44 | argv.indexOf('--watchAll=false') === -1 45 | ) { 46 | // https://github.com/facebook/create-react-app/issues/5210 47 | const hasSourceControl = isInGitRepository() || isInMercurialRepository() 48 | argv.push(hasSourceControl ? '--watch' : '--watchAll') 49 | } 50 | 51 | jest.run(argv) 52 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es5", 5 | "lib": [ 6 | "es6", 7 | "dom" 8 | ], 9 | "sourceMap": true, 10 | "allowJs": true, 11 | "jsx": "react", 12 | "moduleResolution": "node", 13 | "rootDir": "./", 14 | "skipLibCheck": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noImplicitAny": true, 19 | "importHelpers": true, 20 | "allowSyntheticDefaultImports": true, 21 | "resolveJsonModule": true, 22 | "strictNullChecks": true, 23 | "suppressImplicitAnyIndexErrors": true, 24 | "baseUrl": "./", 25 | "paths": { 26 | "~/*": ["./src/*"], 27 | "@/*": ["./src/*"] 28 | }, 29 | // "noUnusedLocals": true 30 | }, 31 | "include": [ 32 | "src" 33 | ], 34 | "exclude": ["**/node_modules"] 35 | } 36 | --------------------------------------------------------------------------------