├── .gitignore ├── dist.zip ├── babel.config.js ├── src ├── assets │ └── img │ │ └── about.jpg ├── vue-shim.d.ts ├── App.vue ├── main.ts ├── unit │ └── decorator.ts ├── router │ └── index.js ├── pages │ ├── about.vue │ ├── home.vue │ ├── login.vue │ └── register.vue └── style │ └── reset.css ├── types └── index.d.ts ├── postcss.config.js ├── index.html ├── upload ├── config.js ├── upload.js └── spinner_style.js ├── webpack ├── webpack.dev.js ├── webpack.base.js └── webpack.prod.js ├── tsconfig.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ -------------------------------------------------------------------------------- /dist.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgwd666/deploy/HEAD/dist.zip -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/env"] 3 | } -------------------------------------------------------------------------------- /src/assets/img/about.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bgwd666/deploy/HEAD/src/assets/img/about.jpg -------------------------------------------------------------------------------- /src/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue"; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue/types/vue' { 2 | interface Vue { 3 | $Message: any, 4 | $axios: any 5 | this: any 6 | } 7 | } -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({ 4 | "overrideBrowserslist": [ 5 | "defaults", 6 | "ie > 9", 7 | "last 2 versions", 8 | "> 1%", 9 | "iOS 7", 10 | "last 3 iOS versions" 11 | ] 12 | }) 13 | ] 14 | }; -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import './style/reset.css' 5 | import axios from 'axios' 6 | // import iView from 'iview'; 7 | // import 'iview/dist/styles/iview.css'; 8 | // import './unit/decorator' 9 | // Vue.use(iView); 10 | 11 | Vue.prototype.$axios = axios; 12 | 13 | new Vue({ 14 | el: '#root', 15 | router, 16 | render: h => h(App) 17 | }) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title%> 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /upload/config.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.freeze({ 2 | development: {//测试 3 | SERVER_PATH: '112.71.62.21', // ssh地址 服务器地址 4 | SSH_USER: 'root', // ssh 用户名 5 | //方式一 用秘钥登录服务器(推荐), private 本机私钥文件地址(需要在服务器用户目录 一般是 /root/.ssh/authorized_keys 配置公钥 并该文件权限为 600, (.ssh文件夹一般默认隐藏) 6 | // PRIVATE_KEY: 'C:/Users/Html5/.ssh/id_rsa', 7 | PASSWORD: '', //方式二 用密码连接服务器 8 | PATH: '/usr/local/nginx/html/vue' // 需要上传的服务器目录地址 如 /usr/local/nginx/html 9 | }, 10 | production: {//正式 11 | SERVER_PATH: '', 12 | SSH_USER: 'root', 13 | PRIVATE_KEY: '', 14 | PATH: '/test/html' 15 | } 16 | }) -------------------------------------------------------------------------------- /src/unit/decorator.ts: -------------------------------------------------------------------------------- 1 | function app(target) { 2 | target.user = '777'; 3 | } 4 | 5 | @app 6 | class test { 7 | user: string; 8 | constructor(){ 9 | this.user = '666'; 10 | } 11 | } 12 | console.log(test['user']); // 777 13 | console.log(new test().user);// 666 14 | 15 | 16 | //多层 17 | function app2(user: string) { 18 | return function(target){ 19 | target.user = user; 20 | } 21 | } 22 | 23 | @app2('bgbg') 24 | class test2 { 25 | 26 | } 27 | console.log(test2['user']); // bgbg 28 | 29 | 30 | //修改原型 31 | function app3(target) { 32 | target.prototype.user = '777'; 33 | } 34 | 35 | @app3 36 | class test3 { 37 | user: string; 38 | constructor(){ 39 | this.user = '666'; 40 | } 41 | } 42 | console.log(new test3()['user']);// 666 43 | 44 | @app3 45 | class test4 { 46 | constructor(){ 47 | } 48 | } 49 | console.log(new test4()['user']);// 777 -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router' 3 | 4 | Vue.use(VueRouter); 5 | 6 | export default new VueRouter({ 7 | // mode: 'history', 8 | // base: '/vue/', 9 | mdoe: 'hash', 10 | routes: [ 11 | { 12 | path: '/', 13 | redirect: '/home' 14 | }, 15 | { 16 | path: '/home', 17 | name: 'home', 18 | meta: { title: '主页' }, 19 | component: () => import('../pages/home.vue') 20 | }, 21 | { 22 | path: '/about', 23 | name: 'about', 24 | meta: { title: '关于' }, 25 | component: () => import('../pages/about.vue') 26 | }, 27 | { 28 | path: '/login', 29 | name: 'login', 30 | meta: { title: '登录' }, 31 | component: () => import('../pages/login.vue') 32 | }, 33 | { 34 | path: '/register', 35 | name: 'register', 36 | meta: { title: '注册' }, 37 | component: () => import('../pages/register.vue') 38 | }, 39 | { 40 | path: '*', 41 | redirect: '/home' 42 | } 43 | ] 44 | }) -------------------------------------------------------------------------------- /webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const baseConfig = require('./webpack.base'); 3 | const webpack = require('webpack'); 4 | module.exports = merge(baseConfig,{ 5 | mode: 'development', 6 | output: { 7 | filename: '[name].js', 8 | }, 9 | // 开启devServer 10 | devServer: { 11 | host: '0.0.0.0', 12 | port: 8080, 13 | hot: true, 14 | // publicPath: '/static/', 15 | publicPath: '/', 16 | // open: true, // 自动打开浏览器 17 | historyApiFallback: true, 18 | // historyApiFallback: { 19 | // rewrites: [{ 20 | // from: /\*/, 21 | // to: '/vue/index.html' 22 | // }] 23 | // }, //然后随便访问个不存在的路径都会重定向 24 | proxy: { 25 | '/apis': { 26 | target: 'http://api.budejie.com', 27 | changeOrigin: true, 28 | pathRewrite: { 29 | '^/apis': '' 30 | } 31 | } 32 | } 33 | 34 | }, 35 | // 开启调试 36 | devtool: 'cheap-module-eval-source-map ', 37 | module: { 38 | rules: [ 39 | { 40 | test: /\.(css|scss)$/, 41 | use: ['vue-style-loader', 'css-loader',"sass-loader"] 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new webpack.HotModuleReplacementPlugin() 47 | ] 48 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // 编译选项 3 | "compilerOptions": { 4 | // 输出目录 5 | "outDir": "./output", 6 | // 是否包含可以用于 debug 的 sourceMap 7 | "sourceMap": true, 8 | // 以严格模式解析 9 | "strict": true, 10 | // 采用的模块系统 11 | "module": "esnext", 12 | // 如何处理模块 13 | "moduleResolution": "node", 14 | // 编译输出目标 ES 版本 15 | "target": "es5", 16 | // 允许从没有设置默认导出的模块中默认导入 17 | "allowSyntheticDefaultImports": true, 18 | // 将每个文件作为单独的模块 19 | "isolatedModules": false, 20 | // 启用装饰器 21 | "experimentalDecorators": true, 22 | // 启用设计类型元数据(用于反射) 23 | "emitDecoratorMetadata": true, 24 | // 在表达式和声明上有隐含的any类型时报错 25 | "noImplicitAny": false, 26 | // 不是函数的所有返回路径都有返回值时报错。 27 | "noImplicitReturns": true, 28 | // 从 tslib 导入外部帮助库: 比如__extends,__rest等 29 | "importHelpers": true, 30 | // 编译过程中打印文件名 31 | "listFiles": true, 32 | // 移除注释 33 | "removeComments": true, 34 | "suppressImplicitAnyIndexErrors": true, 35 | // 允许编译javascript文件 36 | "allowJs": true, 37 | // 解析非相对模块名的基准目录 38 | "baseUrl": "./", 39 | // 指定特殊模块的路径 40 | "typeRoots": [ //类型扩展 41 | "types/index.d.ts", 42 | "./node_modules/vue/types" 43 | ], 44 | "paths": { 45 | // "jquery": [ 46 | // "node_modules/jquery/dist/jquery" 47 | // ] 48 | }, 49 | // 编译过程中需要引入的库文件的列表 50 | "lib": [ 51 | "dom", 52 | "es2015", 53 | "es2015.promise" 54 | ] 55 | } 56 | } -------------------------------------------------------------------------------- /src/pages/about.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 35 | 36 | 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-cli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "serve": "webpack-dev-server --config webpack/webpack.dev.js", 7 | "build": "npx webpack --config webpack/webpack.prod.js", 8 | "deploy": "node ./upload/upload.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.19.0", 15 | "vue": "^2.6.10", 16 | "vue-router": "^3.0.7", 17 | "vuex": "^3.1.1" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.5.5", 21 | "@babel/preset-env": "^7.5.5", 22 | "autoprefixer": "^9.7.1", 23 | "babel-loader": "^8.0.6", 24 | "camelcase": "^5.3.1", 25 | "chalk": "^2.4.2", 26 | "clean-webpack-plugin": "^3.0.0", 27 | "compressing": "^1.4.0", 28 | "css-loader": "^3.1.0", 29 | "decamelize": "^3.2.0", 30 | "file-loader": "^4.1.0", 31 | "html-webpack-plugin": "^3.2.0", 32 | "import-local": "^3.0.2", 33 | "inquirer": "^7.0.0", 34 | "mini-css-extract-plugin": "^0.8.0", 35 | "ms": "^2.1.2", 36 | "node-sass": "^4.12.0", 37 | "node-ssh": "^6.0.0", 38 | "optimize-css-assets-webpack-plugin": "^5.0.3", 39 | "ora": "^4.0.2", 40 | "p-limit": "^2.2.0", 41 | "postcss-loader": "^3.0.0", 42 | "resolve-from": "^5.0.0", 43 | "sass-loader": "^7.1.0", 44 | "shelljs": "^0.8.3", 45 | "strip-ansi": "^5.2.0", 46 | "ts-loader": "^6.0.4", 47 | "tslint": "^5.18.0", 48 | "tslint-config-standard": "^8.0.1", 49 | "tslint-loader": "^3.5.4", 50 | "typescript": "^3.5.3", 51 | "uglifyjs-webpack-plugin": "^2.2.0", 52 | "url-loader": "^2.1.0", 53 | "vue-class-component": "^7.1.0", 54 | "vue-loader": "^15.7.1", 55 | "vue-property-decorator": "^8.2.1", 56 | "vue-style-loader": "^4.1.2", 57 | "vue-template-compiler": "^2.6.10", 58 | "webpack": "^4.38.0", 59 | "webpack-bundle-analyzer": "^3.4.1", 60 | "webpack-cli": "^3.3.6", 61 | "webpack-dev-server": "^3.7.2", 62 | "webpack-merge": "^4.2.1", 63 | "ws": "^7.1.2", 64 | "yargs-parser": "^13.1.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /webpack/webpack.base.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const { VueLoaderPlugin } = require('vue-loader'); 3 | 4 | module.exports = { 5 | entry: './src/main.ts', 6 | resolve: { 7 | // 这里可以配置些文件后缀,用于写路径时可省略后缀(会影响打包速度,不建议配太多) 8 | extensions: [ '.tsx', '.ts', '.js', 'vue'] 9 | }, 10 | node: { crypto: true, stream: true, fs: 'empty', net: 'empty' }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.vue$/, 15 | loader: 'vue-loader' 16 | }, 17 | { 18 | test: /\.js$/, 19 | exclude: /node_modules/, 20 | loader: 'babel-loader' 21 | }, 22 | // { 23 | // test: /\.ts$/, //eslint 24 | // exclude: /node_modules/, 25 | // enforce: 'pre', 26 | // loader: 'tslint-loader' 27 | // }, 28 | { 29 | // 识别.ts或.tsx文件 30 | test: /\.tsx?$/, 31 | exclude: /node_modules/, 32 | use: [ 33 | "babel-loader", 34 | { 35 | loader: "ts-loader", 36 | options: { appendTsxSuffixTo: [/\.vue$/] } 37 | } 38 | ] 39 | }, 40 | { 41 | test: /\.(gif|png|jpg|jpeg|webp|svg|ttf|woff2|woff)$/, 42 | use: [{ 43 | loader: 'url-loader', 44 | options: { 45 | // 当图片大于801k时,交给file-loader处理,否则url-loader会把图片src转成base64编码 46 | limit: 1024 * 8, 47 | // // 打包后的文件名 // name: '[name].[hash:8].[ext]', 48 | // // 打包路径 // outputPath: 'img/' 49 | //结合 50 | name: 'img/[name].[hash:8].[ext]', 51 | } 52 | }] 53 | } 54 | ] 55 | }, 56 | externals: { 57 | // puppeteer: 'require("puppeteer")' 58 | }, 59 | plugins: [ 60 | new VueLoaderPlugin(), 61 | new HtmlWebpackPlugin({ 62 | // 设置模板title 63 | title: 'vue-cli', 64 | 65 | // 生成的模板名称,默认 'index.html', 规则类似output 66 | filename: 'index.html', 67 | 68 | // 指定生成的html文件依赖的模板 69 | template: './index.html', 70 | 71 | // 插入meta标签 72 | meta: { 73 | 'apple-touch-fullscreen': 'yes', 74 | } 75 | }) 76 | ] 77 | } -------------------------------------------------------------------------------- /src/pages/home.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 60 | 61 | -------------------------------------------------------------------------------- /src/style/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video, input { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font-weight: normal; 19 | vertical-align: baseline; 20 | } 21 | 22 | /* HTML5 display-role reset for older browsers */ 23 | article, aside, details, figcaption, figure, 24 | footer, header, menu, nav, section { 25 | display: block; 26 | } 27 | 28 | body { 29 | line-height: 1; 30 | font-family: 'MicrosoftYaHei','PingFangSC-Regular',Arial,'sans-serif'; 31 | font-size: 6vw; 32 | } 33 | 34 | .flex-center{ 35 | display: flex; 36 | align-items: center; 37 | justify-content: center; 38 | } 39 | .flex-bt{ 40 | display: flex; 41 | align-items: center; 42 | justify-content: space-between; 43 | } 44 | .flex-ad{ 45 | display: flex; 46 | align-items: center; 47 | justify-content: space-around; 48 | } 49 | .flex-cl{ 50 | display: flex; 51 | align-items: center; 52 | justify-content: center; 53 | flex-direction: column; 54 | } 55 | 56 | blockquote, q { 57 | quotes: none; 58 | } 59 | 60 | blockquote:before, blockquote:after, 61 | q:before, q:after { 62 | content: none; 63 | } 64 | 65 | table { 66 | border-collapse: collapse; 67 | border-spacing: 0; 68 | } 69 | 70 | /* custom */ 71 | a { 72 | color: #7e8c8d; 73 | text-decoration: none; 74 | backface-visibility: hidden; 75 | } 76 | 77 | li { 78 | list-style: none; 79 | } 80 | 81 | ::-webkit-scrollbar { 82 | width: 5px; 83 | height: 5px; 84 | } 85 | 86 | ::-webkit-scrollbar-track-piece { 87 | background-color: rgba(0, 0, 0, 0.2); 88 | border-radius: 6px; 89 | } 90 | 91 | ::-webkit-scrollbar-thumb:vertical { 92 | height: 5px; 93 | background-color: rgba(125, 125, 125, 0.7); 94 | border-radius: 6px; 95 | } 96 | 97 | ::-webkit-scrollbar-thumb:horizontal { 98 | width: 5px; 99 | background-color: rgba(125, 125, 125, 0.7); 100 | border-radius: 6px; 101 | } 102 | 103 | html, body { 104 | width: 100%; 105 | height: 100%; 106 | } 107 | 108 | body { 109 | -webkit-text-size-adjust: none; 110 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 111 | } -------------------------------------------------------------------------------- /webpack/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const baseConfig = require('./webpack.base'); 3 | const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); //提取css 5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');//压缩css插件 6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); //混淆js 7 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 8 | 9 | module.exports = merge(baseConfig,{ 10 | mode: 'production', 11 | output: { 12 | filename: '[name].[contenthash:10].js', 13 | }, 14 | optimization: { 15 | usedExports: true, // production模式下默认开启 //摇树优化 引用但未使用的将删除 16 | // 配置代码分割 17 | splitChunks: { 18 | // 要分割哪些模块:all(推荐), async(默认,只分隔异步代码), and initial 19 | // chunks: 'all' 20 | cacheGroups: { 21 | vendors: false, 22 | default: false, 23 | commons: { 24 | minSize: 30000, 25 | minChunks: 3, 26 | reuseExistingChunk: true // 这个配置允许我们使用已经存在的代码块 27 | } 28 | } 29 | }, 30 | minimizer: [ 31 | new UglifyJsPlugin({ 32 | exclude: /\.min\.js$/, // 过滤掉以".min.js"结尾的文件,我们认为这个后缀本身就是已经压缩好的代码,没必要进行二次压缩 33 | cache: true, 34 | parallel: true, // 开启并行压缩,充分利用cpu 35 | sourceMap: false, //引用源 36 | extractComments: false, // 移除注释 37 | uglifyOptions: { 38 | warnings: false, 39 | compress: { 40 | unused: true, 41 | drop_console: true, //删除console 42 | drop_debugger: true 43 | }, 44 | output: { 45 | comments: false 46 | } 47 | } 48 | }), 49 | new OptimizeCSSAssetsPlugin({ 50 | assetNameRegExp: /\.css$/g, 51 | cssProcessor: require('cssnano'), 52 | cssProcessorPluginOptions: { 53 | preset: ['default', { 54 | discardComments: { //对注释的处理 55 | removeAll: true, 56 | }, 57 | // autoprefixer: { disable: true }, 58 | normalizeUnicode: false //避免使用 unicode-range 的时候会产生乱码 59 | }] 60 | }, 61 | canPrint: true //是否打印处理过程中的日志 62 | }) 63 | ] 64 | }, 65 | // 开启调试 66 | // devtool: 'source-map', 67 | module: { 68 | rules: [ 69 | { 70 | test: /\.(c|sc)ss$/, 71 | use: [{loader: MiniCssExtractPlugin.loader}, 'css-loader','postcss-loader','sass-loader'] 72 | } 73 | ] 74 | }, 75 | plugins: [ 76 | // new BundleAnalyzerPlugin(), 77 | new CleanWebpackPlugin(), 78 | new MiniCssExtractPlugin({ 79 | filename: '[name].[contenthash:8].css', 80 | chunkFilename: '[name].chunk.css' 81 | }) 82 | ] 83 | }); -------------------------------------------------------------------------------- /upload/upload.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') //命令行颜色 2 | const ora = require('ora') // 加载流程动画 3 | const spinner_style = require('./spinner_style') //加载动画样式 4 | const shell = require('shelljs') // 执行shell命令 5 | const node_ssh = require('node-ssh') // ssh连接服务器 6 | const inquirer = require('inquirer') //命令行交互 7 | const zipFile = require('compressing')// 压缩zip 8 | const fs = require('fs') // nodejs内置文件模块 9 | const path = require('path') // nodejs内置路径模块 10 | const CONFIG = require('./config') // 配置 11 | 12 | const SSH = new node_ssh(); 13 | let config; // 用于保存 inquirer 命令行交互后选择正式|测试版的配置 14 | 15 | //logs 16 | const defaultLog = log => console.log(chalk.blue(`---------------- ${log} ----------------`)); 17 | const errorLog = log => console.log(chalk.red(`---------------- ${log} ----------------`)); 18 | const successLog = log => console.log(chalk.green(`---------------- ${log} ----------------`)); 19 | 20 | //文件夹目录 21 | const distDir = path.resolve(__dirname, '../dist'); //待打包 22 | const distZipPath = path.resolve(__dirname, `../dist.zip`); //打包后地址(dist.zip是文件名,不需要更改, 主要在config中配置 PATH 即可) 23 | 24 | 25 | //项目打包代码 npm run build 26 | const compileDist = async () => { 27 | const loading = ora( defaultLog('项目开始打包') ).start(); 28 | loading.spinner = spinner_style.arrow4; 29 | shell.cd(path.resolve(__dirname, '../')); 30 | const res = await shell.exec('npm run build'); //执行shell 打包命令 31 | loading.stop(); 32 | if(res.code === 0) { 33 | successLog('项目打包成功!'); 34 | } else { 35 | errorLog('项目打包失败, 请重试!'); 36 | process.exit(); //退出流程 37 | } 38 | } 39 | 40 | //压缩代码 41 | const zipDist = async ()=>{ 42 | defaultLog('项目开始压缩'); 43 | try { 44 | await zipFile.zip.compressDir(distDir, distZipPath) 45 | successLog('压缩成功!'); 46 | } catch (error) { 47 | errorLog(error); 48 | errorLog('压缩失败, 退出程序!'); 49 | process.exit(); //退出流程 50 | } 51 | } 52 | 53 | //连接服务器 54 | const connectSSH = async ()=>{ 55 | const loading = ora( defaultLog('正在连接服务器') ).start(); 56 | loading.spinner = spinner_style.arrow4; 57 | try { 58 | await SSH.connect({ 59 | host: config.SERVER_PATH, 60 | username: config.SSH_USER, 61 | // privateKey: config.PRIVATE_KEY, //秘钥登录(推荐) 方式一 62 | password: config.PASSWORD // 密码登录 方式二 63 | }); 64 | successLog('SSH连接成功!'); 65 | } catch (error) { 66 | errorLog(error); 67 | errorLog('SSH连接失败!'); 68 | process.exit(); //退出流程 69 | } 70 | loading.stop(); 71 | } 72 | 73 | //线上执行命令 74 | /** 75 | * 76 | * @param {String} command 命令操作 如 ls 77 | */ 78 | const runCommand = async (command)=> { 79 | const result = await SSH.exec(command, [], { cwd: config.PATH}) 80 | // defaultLog(result); 81 | } 82 | 83 | //清空线上目标目录里的旧文件 84 | const clearOldFile = async () =>{ 85 | const commands = ['ls', 'rm -rf *']; 86 | await Promise.all(commands.map(async (it)=>{ 87 | return await runCommand(it); 88 | })); 89 | } 90 | 91 | //传送zip文件到服务器 92 | const uploadZipBySSH = async () =>{ 93 | //连接ssh 94 | await connectSSH(); 95 | //线上目标文件清空 96 | await clearOldFile(); 97 | const loading = ora( defaultLog('准备上传文件') ).start(); 98 | loading.spinner = spinner_style.arrow4; 99 | try { 100 | await SSH.putFiles([{ local: distZipPath, remote: config.PATH + '/dist.zip' }]); //local 本地 ; remote 服务器 ; 101 | successLog('上传成功!'); 102 | loading.text = '正在解压文件'; 103 | await runCommand('unzip ./dist.zip'); //解压 104 | await runCommand(`rm -rf ${config.PATH}/dist.zip`); //解压完删除线上压缩包 105 | //将目标目录的dist里面文件移出到目标文件 106 | //举个例子 假如我们部署在 /test/html 这个目录下 只有一个网站, 那么上传解压后的文件在 /test/html/dist 里 107 | //需要将 dist 目录下的文件 移出到 /test/html ; 多网站情况, 如 /test/html/h5 或者 /test/html/admin 都和上面同样道理 108 | await runCommand(`mv -f ${config.PATH}/dist/* ${config.PATH}`); 109 | await runCommand(`rm -rf ${config.PATH}/dist`); //移出后删除 dist 文件夹 110 | SSH.dispose(); //断开连接 111 | } catch (error) { 112 | errorLog(error); 113 | errorLog('上传失败!'); 114 | process.exit(); //退出流程 115 | } 116 | loading.stop(); 117 | } 118 | 119 | 120 | 121 | //------------发布程序--------------- 122 | const runUploadTask = async () => { 123 | console.log(chalk.yellow(`---------> 欢迎使用 波哥牌 2020年自动部署工具 <---------`)); 124 | //打包 125 | await compileDist(); 126 | //压缩 127 | await zipDist(); 128 | //连接服务器上传文件 129 | await uploadZipBySSH(); 130 | successLog('大吉大利, 部署成功!'); 131 | process.exit(); 132 | } 133 | 134 | // 开始前的配置检查 135 | /** 136 | * 137 | * @param {Object} conf 配置对象 138 | */ 139 | const checkConfig = (conf) =>{ 140 | const checkArr = Object.entries(conf); 141 | checkArr.map(it=>{ 142 | const key = it[0]; 143 | if(key === 'PATH' && conf[key] === '/') { //上传zip前会清空目标目录内所有文件 144 | errorLog('PATH 不能是服务器根目录!'); 145 | process.exit(); //退出流程 146 | } 147 | if(!conf[key]) { 148 | errorLog(`配置项 ${key} 不能为空`); 149 | process.exit(); //退出流程 150 | } 151 | }) 152 | } 153 | 154 | // 执行交互后 启动发布程序 155 | inquirer 156 | .prompt([{ 157 | type: 'list', 158 | message: '请选择发布环境', 159 | name: 'env', 160 | choices: [{ 161 | name: '测试环境', 162 | value: 'development' 163 | },{ 164 | name: '正式环境', 165 | value: 'production' 166 | }] 167 | }]) 168 | .then(answers => { 169 | config = CONFIG[answers.env]; 170 | checkConfig(config); // 检查 171 | runUploadTask(); // 发布 172 | }); 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 永恒的前言 2 | * * * 3 | ### 已发布npm包 bgwd-deploy 在我github项目中 4 | 移步[bgwd-deploy](https://github.com/bgwd666/bgwd-deploy) 5 | 6 | 7 | (如果您觉得对您有一点点帮助 点个star 那就非常感谢了) 8 | 9 | (这个git项目是我自己搭的一个比较low的vue脚手架,集成ts) 10 | 11 | (主要看 自动部署 在 upload 目录 ) 12 | 13 | 本demo执行: npm 或 cnpm i 安装依赖 14 | 15 | **运行 npm run serve** 16 | 17 | **打包 npm run build** 18 | 19 | **自动部署 npm run deploy (需要配置 upload目录下的 config.js 配置项)** 20 | 21 | 教程在下面, 或者点文章链接 22 | 23 | [文章地址-思否](https://segmentfault.com/a/1190000020994461?share_user=1030000018633764) 24 | 25 | [文章地址-掘金](https://juejin.im/post/5dce6b2b5188254eee54d77a) 26 | 27 | **简单实用的前端部署, 一条命令搞定, 省去繁琐的步骤!** 28 | 29 | 主要是 nodejs shelljs(命令行命令) node-ssh(连接服务器) 30 | 31 | [项目git 地址](https://github.com/bgwd666/deploy) 32 | 33 | 34 | #### 如何在你项目中使用( 五步曲 ) 35 | 36 | 37 | **第一步, 把项目里 upload 文件夹复制到你项目根目录** 38 | 39 | ![avatar](https://segmentfault.com/img/bVbAfjP?w=505&h=219) 40 | 41 | **第二步, 下载该js相关依赖** 42 | npm 或 cnpm i chalk ora shelljs node-ssh inquirer compressing -D 43 | 44 | **第三步, 打开 upload/config.js 配置文件, 配置ssh地址, 用户名, 验证方式,需要上传的目录** 45 | 46 | ![avatar](https://segmentfault.com/img/bVbAfkZ?w=858&h=376) 47 | 48 | **第四步, 在你项目中 package.json 文件中 加上 "deploy": "node ./upload/upload.js"** 49 | 50 | ![avatar](https://segmentfault.com/img/bVbAflU?w=721&h=267) 51 | 52 | 53 | **最后 在命令行输入 npm run deploy 选择发布环境 然后就ojbk了** 54 | 55 | ![avatar](https://segmentfault.com/img/bVbAfmw?w=566&h=115) 56 | ![avatar](https://segmentfault.com/img/bVbAfmQ?w=544&h=150) 57 | ![avatar](https://segmentfault.com/img/bVbAfmY?w=789&h=177) 58 | ![avatar](https://segmentfault.com/img/bVbAfm1?w=557&h=357) 59 | 60 | 大功告成-- 61 | * * * 62 | 63 | ### 来看主要文件 upload.js 64 | > **首先项目依赖** 65 | ``` 66 | const chalk = require('chalk') //命令行颜色 67 | const ora = require('ora') // 加载流程动画 68 | const spinner_style = require('./spinner_style') //加载动画样式 69 | const shell = require('shelljs') // 执行shell命令 70 | const node_ssh = require('node-ssh') // ssh连接服务器 71 | const inquirer = require('inquirer') //命令行交互 72 | const zipFile = require('compressing')// 压缩zip 73 | const fs = require('fs') // nodejs内置文件模块 74 | const path = require('path') // nodejs内置路径模块 75 | const CONFIG = require('./config') // 配置 76 | ``` 77 | > **一些常量,变量和logs** 78 | ``` 79 | const SSH = new node_ssh(); 80 | let config; // 用于保存 inquirer 命令行交互后选择正式|测试版的配置 81 | 82 | //logs 83 | const defaultLog = log => console.log(chalk.blue(`---------------- ${log} ----------------`)); 84 | const errorLog = log => console.log(chalk.red(`---------------- ${log} ----------------`)); 85 | const successLog = log => console.log(chalk.green(`---------------- ${log} ----------------`)); 86 | 87 | //文件夹目录 88 | const distDir = path.resolve(__dirname, '../dist'); //待打包 89 | const distZipPath = path.resolve(__dirname, `../dist.zip`); 90 | //打包后地址(dist.zip是文件名,不需要更改, 主要在config中配置 PATH 即可) 91 | ``` 92 | > **首先 执行项目打包命令** 93 | ``` 94 | //项目打包代码 npm run build 95 | const compileDist = async () => { 96 | const loading = ora( defaultLog('项目开始打包') ).start(); 97 | loading.spinner = spinner_style.arrow4; 98 | shell.cd(path.resolve(__dirname, '../')); 99 | const res = await shell.exec('npm run build'); //执行shell 打包命令 100 | loading.stop(); 101 | if(res.code === 0) { 102 | successLog('项目打包成功!'); 103 | } else { 104 | errorLog('项目打包失败, 请重试!'); 105 | process.exit(); //退出流程 106 | } 107 | } 108 | ``` 109 | > **然后 对打包的代码 /dist 目录打包 (如果不是dist, 请更改上面的 disDir 常量结尾的dist)** 110 | ``` 111 | //压缩代码 112 | const zipDist = async ()=>{ 113 | defaultLog('项目开始压缩'); 114 | try { 115 | await zipFile.zip.compressDir(distDir, distZipPath) 116 | successLog('压缩成功!'); 117 | } catch (error) { 118 | errorLog(error); 119 | errorLog('压缩失败, 退出程序!'); 120 | process.exit(); //退出流程 121 | } 122 | } 123 | ``` 124 | > **再然后 通过ssh连接服务器 有两种方式: 一是通过秘钥连接(推荐), 二是密码连接** 125 | > **秘钥连接需要把本机公钥放服务器指定目录 (在upload/config.js 有说明)** 126 | ![avatar](https://segmentfault.com/img/bVbAfI1?w=871&h=102) 127 | ``` 128 | //连接服务器 129 | const connectSSH = async ()=>{ 130 | const loading = ora( defaultLog('正在连接服务器') ).start(); 131 | loading.spinner = spinner_style.arrow4; 132 | try { 133 | await SSH.connect({ 134 | host: config.SERVER_PATH, 135 | username: config.SSH_USER, 136 | // privateKey: config.PRIVATE_KEY, //秘钥登录(推荐) 方式一 137 | password: config.PASSWORD // 密码登录 方式二 138 | }); 139 | successLog('SSH连接成功!'); 140 | } catch (error) { 141 | errorLog(error); 142 | errorLog('SSH连接失败!'); 143 | process.exit(); //退出流程 144 | } 145 | loading.stop(); 146 | } 147 | ``` 148 | > **紧接着 通过ssh执行线上命令 进行目标目录清空, 然后上传zip到服务器 并解压 等操作** 149 | ``` 150 | //线上执行命令 151 | /** 152 | * 153 | * @param {String} command 命令操作 如 ls 154 | */ 155 | const runCommand = async (command)=> { 156 | const result = await SSH.exec(command, [], { cwd: config.PATH}) 157 | // defaultLog(result); 158 | } 159 | 160 | //清空线上目标目录里的旧文件 161 | const clearOldFile = async () =>{ 162 | const commands = ['ls', 'rm -rf *']; 163 | await Promise.all(commands.map(async (it)=>{ 164 | return await runCommand(it); 165 | })); 166 | } 167 | 168 | //传送zip文件到服务器 169 | const uploadZipBySSH = async () =>{ 170 | //连接ssh 171 | await connectSSH(); 172 | //线上目标文件清空 173 | await clearOldFile(); 174 | const loading = ora( defaultLog('准备上传文件') ).start(); 175 | loading.spinner = spinner_style.arrow4; 176 | try { 177 | await SSH.putFiles([{ local: distZipPath, remote: config.PATH + '/dist.zip' }]); //local 本地 ; remote 服务器 ; 178 | successLog('上传成功!'); 179 | loading.text = '正在解压文件'; 180 | await runCommand('unzip ./dist.zip'); //解压 181 | await runCommand(`rm -rf ${config.PATH}/dist.zip`); //解压完删除线上压缩包 182 | //将目标目录的dist里面文件移出到目标文件 183 | //举个例子 假如我们部署在 /test/html 这个目录下 只有一个网站, 那么上传解压后的文件在 /test/html/dist 里 184 | //需要将 dist 目录下的文件 移出到 /test/html ; 多网站情况, 如 /test/html/h5 或者 /test/html/admin 都和上面同样道理 185 | await runCommand(`mv -f ${config.PATH}/dist/* ${config.PATH}`); 186 | await runCommand(`rm -rf ${config.PATH}/dist`); //移出后删除 dist 文件夹 187 | SSH.dispose(); //断开连接 188 | } catch (error) { 189 | errorLog(error); 190 | errorLog('上传失败!'); 191 | process.exit(); //退出流程 192 | } 193 | loading.stop(); 194 | } 195 | ``` 196 | > **把这些整合在一个函数** 197 | ``` 198 | //------------发布程序--------------- 199 | const runUploadTask = async () => { 200 | console.log(chalk.yellow(`---------> 欢迎使用 波哥牌 2020年自动部署工具 <---------`)); 201 | //打包 202 | await compileDist(); 203 | //压缩 204 | await zipDist(); 205 | //连接服务器上传文件 206 | await uploadZipBySSH(); 207 | successLog('大吉大利, 部署成功!'); 208 | process.exit(); 209 | } 210 | ``` 211 | > **发布前的检查配置** 212 | ``` 213 | // 开始前的配置检查 214 | /** 215 | * 216 | * @param {Object} conf 配置对象 217 | */ 218 | const checkConfig = (conf) =>{ 219 | const checkArr = Object.entries(conf); 220 | checkArr.map(it=>{ 221 | const key = it[0]; 222 | if(key === 'PATH' && conf[key] === '/') { //上传zip前会清空目标目录内所有文件 223 | errorLog('PATH 不能是服务器根目录!'); 224 | process.exit(); //退出流程 225 | } 226 | if(!conf[key]) { 227 | errorLog(`配置项 ${key} 不能为空`); 228 | process.exit(); //退出流程 229 | } 230 | }) 231 | } 232 | ``` 233 | > **执行交互 选择发布环境 然后启动发布程序** 234 | ``` 235 | // 执行交互后 启动发布程序 236 | inquirer 237 | .prompt([{ 238 | type: 'list', 239 | message: '请选择发布环境', 240 | name: 'env', 241 | choices: [{ 242 | name: '测试环境', 243 | value: 'development' 244 | },{ 245 | name: '正式环境', 246 | value: 'production' 247 | }] 248 | }]) 249 | .then(answers => { 250 | config = CONFIG[answers.env]; 251 | checkConfig(config); // 检查 252 | runUploadTask(); // 发布 253 | }); 254 | ``` 255 | > **大功告成** 256 | 257 | * * * 258 | ### 结尾 259 | **咳咳, 放心, 不会有公众号啥广告, 也不会求打赏, 如果您觉得对您有一点点帮助 点个赞或者去GitHub点个star 那就非常感谢了** 260 | -------------------------------------------------------------------------------- /src/pages/login.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 154 | 265 | -------------------------------------------------------------------------------- /src/pages/register.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 181 | 280 | -------------------------------------------------------------------------------- /upload/spinner_style.js: -------------------------------------------------------------------------------- 1 | const style = { 2 | "dots": { 3 | "interval": 80, 4 | "frames": [ 5 | "⠋", 6 | "⠙", 7 | "⠹", 8 | "⠸", 9 | "⠼", 10 | "⠴", 11 | "⠦", 12 | "⠧", 13 | "⠇", 14 | "⠏" 15 | ] 16 | }, 17 | "dots2": { 18 | "interval": 80, 19 | "frames": [ 20 | "⣾", 21 | "⣽", 22 | "⣻", 23 | "⢿", 24 | "⡿", 25 | "⣟", 26 | "⣯", 27 | "⣷" 28 | ] 29 | }, 30 | "dots3": { 31 | "interval": 80, 32 | "frames": [ 33 | "⠋", 34 | "⠙", 35 | "⠚", 36 | "⠞", 37 | "⠖", 38 | "⠦", 39 | "⠴", 40 | "⠲", 41 | "⠳", 42 | "⠓" 43 | ] 44 | }, 45 | "dots4": { 46 | "interval": 80, 47 | "frames": [ 48 | "⠄", 49 | "⠆", 50 | "⠇", 51 | "⠋", 52 | "⠙", 53 | "⠸", 54 | "⠰", 55 | "⠠", 56 | "⠰", 57 | "⠸", 58 | "⠙", 59 | "⠋", 60 | "⠇", 61 | "⠆" 62 | ] 63 | }, 64 | "dots5": { 65 | "interval": 80, 66 | "frames": [ 67 | "⠋", 68 | "⠙", 69 | "⠚", 70 | "⠒", 71 | "⠂", 72 | "⠂", 73 | "⠒", 74 | "⠲", 75 | "⠴", 76 | "⠦", 77 | "⠖", 78 | "⠒", 79 | "⠐", 80 | "⠐", 81 | "⠒", 82 | "⠓", 83 | "⠋" 84 | ] 85 | }, 86 | "dots6": { 87 | "interval": 80, 88 | "frames": [ 89 | "⠁", 90 | "⠉", 91 | "⠙", 92 | "⠚", 93 | "⠒", 94 | "⠂", 95 | "⠂", 96 | "⠒", 97 | "⠲", 98 | "⠴", 99 | "⠤", 100 | "⠄", 101 | "⠄", 102 | "⠤", 103 | "⠴", 104 | "⠲", 105 | "⠒", 106 | "⠂", 107 | "⠂", 108 | "⠒", 109 | "⠚", 110 | "⠙", 111 | "⠉", 112 | "⠁" 113 | ] 114 | }, 115 | "dots7": { 116 | "interval": 80, 117 | "frames": [ 118 | "⠈", 119 | "⠉", 120 | "⠋", 121 | "⠓", 122 | "⠒", 123 | "⠐", 124 | "⠐", 125 | "⠒", 126 | "⠖", 127 | "⠦", 128 | "⠤", 129 | "⠠", 130 | "⠠", 131 | "⠤", 132 | "⠦", 133 | "⠖", 134 | "⠒", 135 | "⠐", 136 | "⠐", 137 | "⠒", 138 | "⠓", 139 | "⠋", 140 | "⠉", 141 | "⠈" 142 | ] 143 | }, 144 | "dots8": { 145 | "interval": 80, 146 | "frames": [ 147 | "⠁", 148 | "⠁", 149 | "⠉", 150 | "⠙", 151 | "⠚", 152 | "⠒", 153 | "⠂", 154 | "⠂", 155 | "⠒", 156 | "⠲", 157 | "⠴", 158 | "⠤", 159 | "⠄", 160 | "⠄", 161 | "⠤", 162 | "⠠", 163 | "⠠", 164 | "⠤", 165 | "⠦", 166 | "⠖", 167 | "⠒", 168 | "⠐", 169 | "⠐", 170 | "⠒", 171 | "⠓", 172 | "⠋", 173 | "⠉", 174 | "⠈", 175 | "⠈" 176 | ] 177 | }, 178 | "dots9": { 179 | "interval": 80, 180 | "frames": [ 181 | "⢹", 182 | "⢺", 183 | "⢼", 184 | "⣸", 185 | "⣇", 186 | "⡧", 187 | "⡗", 188 | "⡏" 189 | ] 190 | }, 191 | "dots10": { 192 | "interval": 80, 193 | "frames": [ 194 | "⢄", 195 | "⢂", 196 | "⢁", 197 | "⡁", 198 | "⡈", 199 | "⡐", 200 | "⡠" 201 | ] 202 | }, 203 | "dots11": { 204 | "interval": 100, 205 | "frames": [ 206 | "⠁", 207 | "⠂", 208 | "⠄", 209 | "⡀", 210 | "⢀", 211 | "⠠", 212 | "⠐", 213 | "⠈" 214 | ] 215 | }, 216 | "dots12": { 217 | "interval": 80, 218 | "frames": [ 219 | "⢀⠀", 220 | "⡀⠀", 221 | "⠄⠀", 222 | "⢂⠀", 223 | "⡂⠀", 224 | "⠅⠀", 225 | "⢃⠀", 226 | "⡃⠀", 227 | "⠍⠀", 228 | "⢋⠀", 229 | "⡋⠀", 230 | "⠍⠁", 231 | "⢋⠁", 232 | "⡋⠁", 233 | "⠍⠉", 234 | "⠋⠉", 235 | "⠋⠉", 236 | "⠉⠙", 237 | "⠉⠙", 238 | "⠉⠩", 239 | "⠈⢙", 240 | "⠈⡙", 241 | "⢈⠩", 242 | "⡀⢙", 243 | "⠄⡙", 244 | "⢂⠩", 245 | "⡂⢘", 246 | "⠅⡘", 247 | "⢃⠨", 248 | "⡃⢐", 249 | "⠍⡐", 250 | "⢋⠠", 251 | "⡋⢀", 252 | "⠍⡁", 253 | "⢋⠁", 254 | "⡋⠁", 255 | "⠍⠉", 256 | "⠋⠉", 257 | "⠋⠉", 258 | "⠉⠙", 259 | "⠉⠙", 260 | "⠉⠩", 261 | "⠈⢙", 262 | "⠈⡙", 263 | "⠈⠩", 264 | "⠀⢙", 265 | "⠀⡙", 266 | "⠀⠩", 267 | "⠀⢘", 268 | "⠀⡘", 269 | "⠀⠨", 270 | "⠀⢐", 271 | "⠀⡐", 272 | "⠀⠠", 273 | "⠀⢀", 274 | "⠀⡀" 275 | ] 276 | }, 277 | "line": { 278 | "interval": 130, 279 | "frames": [ 280 | "-", 281 | "\\", 282 | "|", 283 | "/" 284 | ] 285 | }, 286 | "line2": { 287 | "interval": 100, 288 | "frames": [ 289 | "⠂", 290 | "-", 291 | "–", 292 | "—", 293 | "–", 294 | "-" 295 | ] 296 | }, 297 | "pipe": { 298 | "interval": 100, 299 | "frames": [ 300 | "┤", 301 | "┘", 302 | "┴", 303 | "└", 304 | "├", 305 | "┌", 306 | "┬", 307 | "┐" 308 | ] 309 | }, 310 | "simpleDots": { 311 | "interval": 400, 312 | "frames": [ 313 | ". ", 314 | ".. ", 315 | "...", 316 | " " 317 | ] 318 | }, 319 | "simpleDotsScrolling": { 320 | "interval": 200, 321 | "frames": [ 322 | ". ", 323 | ".. ", 324 | "...", 325 | " ..", 326 | " .", 327 | " " 328 | ] 329 | }, 330 | "star": { 331 | "interval": 70, 332 | "frames": [ 333 | "✶", 334 | "✸", 335 | "✹", 336 | "✺", 337 | "✹", 338 | "✷" 339 | ] 340 | }, 341 | "star2": { 342 | "interval": 80, 343 | "frames": [ 344 | "+", 345 | "x", 346 | "*" 347 | ] 348 | }, 349 | "flip": { 350 | "interval": 70, 351 | "frames": [ 352 | "_", 353 | "_", 354 | "_", 355 | "-", 356 | "`", 357 | "`", 358 | "'", 359 | "´", 360 | "-", 361 | "_", 362 | "_", 363 | "_" 364 | ] 365 | }, 366 | "hamburger": { 367 | "interval": 100, 368 | "frames": [ 369 | "☱", 370 | "☲", 371 | "☴" 372 | ] 373 | }, 374 | "growVertical": { 375 | "interval": 120, 376 | "frames": [ 377 | "▁", 378 | "▃", 379 | "▄", 380 | "▅", 381 | "▆", 382 | "▇", 383 | "▆", 384 | "▅", 385 | "▄", 386 | "▃" 387 | ] 388 | }, 389 | "growHorizontal": { 390 | "interval": 120, 391 | "frames": [ 392 | "▏", 393 | "▎", 394 | "▍", 395 | "▌", 396 | "▋", 397 | "▊", 398 | "▉", 399 | "▊", 400 | "▋", 401 | "▌", 402 | "▍", 403 | "▎" 404 | ] 405 | }, 406 | "balloon": { 407 | "interval": 140, 408 | "frames": [ 409 | " ", 410 | ".", 411 | "o", 412 | "O", 413 | "@", 414 | "*", 415 | " " 416 | ] 417 | }, 418 | "balloon2": { 419 | "interval": 120, 420 | "frames": [ 421 | ".", 422 | "o", 423 | "O", 424 | "°", 425 | "O", 426 | "o", 427 | "." 428 | ] 429 | }, 430 | "noise": { 431 | "interval": 100, 432 | "frames": [ 433 | "▓", 434 | "▒", 435 | "░" 436 | ] 437 | }, 438 | "bounce": { 439 | "interval": 120, 440 | "frames": [ 441 | "⠁", 442 | "⠂", 443 | "⠄", 444 | "⠂" 445 | ] 446 | }, 447 | "boxBounce": { 448 | "interval": 120, 449 | "frames": [ 450 | "▖", 451 | "▘", 452 | "▝", 453 | "▗" 454 | ] 455 | }, 456 | "boxBounce2": { 457 | "interval": 100, 458 | "frames": [ 459 | "▌", 460 | "▀", 461 | "▐", 462 | "▄" 463 | ] 464 | }, 465 | "triangle": { 466 | "interval": 50, 467 | "frames": [ 468 | "◢", 469 | "◣", 470 | "◤", 471 | "◥" 472 | ] 473 | }, 474 | "arc": { 475 | "interval": 100, 476 | "frames": [ 477 | "◜", 478 | "◠", 479 | "◝", 480 | "◞", 481 | "◡", 482 | "◟" 483 | ] 484 | }, 485 | "circle": { 486 | "interval": 120, 487 | "frames": [ 488 | "◡", 489 | "⊙", 490 | "◠" 491 | ] 492 | }, 493 | "squareCorners": { 494 | "interval": 180, 495 | "frames": [ 496 | "◰", 497 | "◳", 498 | "◲", 499 | "◱" 500 | ] 501 | }, 502 | "circleQuarters": { 503 | "interval": 120, 504 | "frames": [ 505 | "◴", 506 | "◷", 507 | "◶", 508 | "◵" 509 | ] 510 | }, 511 | "circleHalves": { 512 | "interval": 50, 513 | "frames": [ 514 | "◐", 515 | "◓", 516 | "◑", 517 | "◒" 518 | ] 519 | }, 520 | "squish": { 521 | "interval": 100, 522 | "frames": [ 523 | "╫", 524 | "╪" 525 | ] 526 | }, 527 | "toggle": { 528 | "interval": 250, 529 | "frames": [ 530 | "⊶", 531 | "⊷" 532 | ] 533 | }, 534 | "toggle2": { 535 | "interval": 80, 536 | "frames": [ 537 | "▫", 538 | "▪" 539 | ] 540 | }, 541 | "toggle3": { 542 | "interval": 120, 543 | "frames": [ 544 | "□", 545 | "■" 546 | ] 547 | }, 548 | "toggle4": { 549 | "interval": 100, 550 | "frames": [ 551 | "■", 552 | "□", 553 | "▪", 554 | "▫" 555 | ] 556 | }, 557 | "toggle5": { 558 | "interval": 100, 559 | "frames": [ 560 | "▮", 561 | "▯" 562 | ] 563 | }, 564 | "toggle6": { 565 | "interval": 300, 566 | "frames": [ 567 | "ဝ", 568 | "၀" 569 | ] 570 | }, 571 | "toggle7": { 572 | "interval": 80, 573 | "frames": [ 574 | "⦾", 575 | "⦿" 576 | ] 577 | }, 578 | "toggle8": { 579 | "interval": 100, 580 | "frames": [ 581 | "◍", 582 | "◌" 583 | ] 584 | }, 585 | "toggle9": { 586 | "interval": 100, 587 | "frames": [ 588 | "◉", 589 | "◎" 590 | ] 591 | }, 592 | "toggle10": { 593 | "interval": 100, 594 | "frames": [ 595 | "㊂", 596 | "㊀", 597 | "㊁" 598 | ] 599 | }, 600 | "toggle11": { 601 | "interval": 50, 602 | "frames": [ 603 | "⧇", 604 | "⧆" 605 | ] 606 | }, 607 | "toggle12": { 608 | "interval": 120, 609 | "frames": [ 610 | "☗", 611 | "☖" 612 | ] 613 | }, 614 | "toggle13": { 615 | "interval": 80, 616 | "frames": [ 617 | "=", 618 | "*", 619 | "-" 620 | ] 621 | }, 622 | "arrow": { 623 | "interval": 100, 624 | "frames": [ 625 | "←", 626 | "↖", 627 | "↑", 628 | "↗", 629 | "→", 630 | "↘", 631 | "↓", 632 | "↙" 633 | ] 634 | }, 635 | "arrow2": { 636 | "interval": 80, 637 | "frames": [ 638 | "⬆️ ", 639 | "↗️ ", 640 | "➡️ ", 641 | "↘️ ", 642 | "⬇️ ", 643 | "↙️ ", 644 | "⬅️ ", 645 | "↖️ " 646 | ] 647 | }, 648 | "arrow3": { 649 | "interval": 120, 650 | "frames": [ 651 | "▹▹▹▹▹", 652 | "▸▹▹▹▹", 653 | "▹▸▹▹▹", 654 | "▹▹▸▹▹", 655 | "▹▹▹▸▹", 656 | "▹▹▹▹▸" 657 | ] 658 | }, 659 | "arrow4": { 660 | "interval": 80, 661 | "frames": [ 662 | "[> ]", 663 | "[>>> ]", 664 | "[>>>>> ]", 665 | "[>>>>>>> ]", 666 | "[>>>>>>>>> ]", 667 | "[>>>>>>>>>>> ]", 668 | "[>>>>>>>>>>>>>]" 669 | ] 670 | }, 671 | "bouncingBar": { 672 | "interval": 80, 673 | "frames": [ 674 | "[ ]", 675 | "[= ]", 676 | "[== ]", 677 | "[=== ]", 678 | "[ ===]", 679 | "[ ==]", 680 | "[ =]", 681 | "[ ]", 682 | "[ =]", 683 | "[ ==]", 684 | "[ ===]", 685 | "[====]", 686 | "[=== ]", 687 | "[== ]", 688 | "[= ]" 689 | ] 690 | }, 691 | "bouncingBall": { 692 | "interval": 80, 693 | "frames": [ 694 | "( ● )", 695 | "( ● )", 696 | "( ● )", 697 | "( ● )", 698 | "( ●)", 699 | "( ● )", 700 | "( ● )", 701 | "( ● )", 702 | "( ● )", 703 | "(● )" 704 | ] 705 | }, 706 | "smiley": { 707 | "interval": 200, 708 | "frames": [ 709 | "😄 ", 710 | "😝 " 711 | ] 712 | }, 713 | "monkey": { 714 | "interval": 300, 715 | "frames": [ 716 | "🙈 ", 717 | "🙈 ", 718 | "🙉 ", 719 | "🙊 " 720 | ] 721 | }, 722 | "hearts": { 723 | "interval": 100, 724 | "frames": [ 725 | "💛 ", 726 | "💙 ", 727 | "💜 ", 728 | "💚 ", 729 | "❤️ " 730 | ] 731 | }, 732 | "clock": { 733 | "interval": 100, 734 | "frames": [ 735 | "🕛 ", 736 | "🕐 ", 737 | "🕑 ", 738 | "🕒 ", 739 | "🕓 ", 740 | "🕔 ", 741 | "🕕 ", 742 | "🕖 ", 743 | "🕗 ", 744 | "🕘 ", 745 | "🕙 ", 746 | "🕚 " 747 | ] 748 | }, 749 | "earth": { 750 | "interval": 180, 751 | "frames": [ 752 | "🌍 ", 753 | "🌎 ", 754 | "🌏 " 755 | ] 756 | }, 757 | "moon": { 758 | "interval": 80, 759 | "frames": [ 760 | "🌑 ", 761 | "🌒 ", 762 | "🌓 ", 763 | "🌔 ", 764 | "🌕 ", 765 | "🌖 ", 766 | "🌗 ", 767 | "🌘 " 768 | ] 769 | }, 770 | "runner": { 771 | "interval": 140, 772 | "frames": [ 773 | "🚶 ", 774 | "🏃 " 775 | ] 776 | }, 777 | "pong": { 778 | "interval": 80, 779 | "frames": [ 780 | "▐⠂ ▌", 781 | "▐⠈ ▌", 782 | "▐ ⠂ ▌", 783 | "▐ ⠠ ▌", 784 | "▐ ⡀ ▌", 785 | "▐ ⠠ ▌", 786 | "▐ ⠂ ▌", 787 | "▐ ⠈ ▌", 788 | "▐ ⠂ ▌", 789 | "▐ ⠠ ▌", 790 | "▐ ⡀ ▌", 791 | "▐ ⠠ ▌", 792 | "▐ ⠂ ▌", 793 | "▐ ⠈ ▌", 794 | "▐ ⠂▌", 795 | "▐ ⠠▌", 796 | "▐ ⡀▌", 797 | "▐ ⠠ ▌", 798 | "▐ ⠂ ▌", 799 | "▐ ⠈ ▌", 800 | "▐ ⠂ ▌", 801 | "▐ ⠠ ▌", 802 | "▐ ⡀ ▌", 803 | "▐ ⠠ ▌", 804 | "▐ ⠂ ▌", 805 | "▐ ⠈ ▌", 806 | "▐ ⠂ ▌", 807 | "▐ ⠠ ▌", 808 | "▐ ⡀ ▌", 809 | "▐⠠ ▌" 810 | ] 811 | }, 812 | "shark": { 813 | "interval": 120, 814 | "frames": [ 815 | "▐|\\____________▌", 816 | "▐_|\\___________▌", 817 | "▐__|\\__________▌", 818 | "▐___|\\_________▌", 819 | "▐____|\\________▌", 820 | "▐_____|\\_______▌", 821 | "▐______|\\______▌", 822 | "▐_______|\\_____▌", 823 | "▐________|\\____▌", 824 | "▐_________|\\___▌", 825 | "▐__________|\\__▌", 826 | "▐___________|\\_▌", 827 | "▐____________|\\▌", 828 | "▐____________/|▌", 829 | "▐___________/|_▌", 830 | "▐__________/|__▌", 831 | "▐_________/|___▌", 832 | "▐________/|____▌", 833 | "▐_______/|_____▌", 834 | "▐______/|______▌", 835 | "▐_____/|_______▌", 836 | "▐____/|________▌", 837 | "▐___/|_________▌", 838 | "▐__/|__________▌", 839 | "▐_/|___________▌", 840 | "▐/|____________▌" 841 | ] 842 | }, 843 | "dqpb": { 844 | "interval": 100, 845 | "frames": [ 846 | "d", 847 | "q", 848 | "p", 849 | "b" 850 | ] 851 | }, 852 | "weather": { 853 | "interval": 100, 854 | "frames": [ 855 | "☀️ ", 856 | "☀️ ", 857 | "☀️ ", 858 | "🌤 ", 859 | "⛅️ ", 860 | "🌥 ", 861 | "☁️ ", 862 | "🌧 ", 863 | "🌨 ", 864 | "🌧 ", 865 | "🌨 ", 866 | "🌧 ", 867 | "🌨 ", 868 | "⛈ ", 869 | "🌨 ", 870 | "🌧 ", 871 | "🌨 ", 872 | "☁️ ", 873 | "🌥 ", 874 | "⛅️ ", 875 | "🌤 ", 876 | "☀️ ", 877 | "☀️ " 878 | ] 879 | }, 880 | "christmas": { 881 | "interval": 400, 882 | "frames": [ 883 | "🌲", 884 | "🎄" 885 | ] 886 | }, 887 | "grenade": { 888 | "interval": 80, 889 | "frames": [ 890 | "، ", 891 | "′ ", 892 | " ´ ", 893 | " ‾ ", 894 | " ⸌", 895 | " ⸊", 896 | " |", 897 | " ⁎", 898 | " ⁕", 899 | " ෴ ", 900 | " ⁓", 901 | " ", 902 | " ", 903 | " " 904 | ] 905 | }, 906 | "point": { 907 | "interval": 125, 908 | "frames": [ 909 | "∙∙∙", 910 | "●∙∙", 911 | "∙●∙", 912 | "∙∙●", 913 | "∙∙∙" 914 | ] 915 | }, 916 | "layer": { 917 | "interval": 150, 918 | "frames": [ 919 | "-", 920 | "=", 921 | "≡" 922 | ] 923 | }, 924 | "betaWave": { 925 | "interval": 80, 926 | "frames": [ 927 | "ρββββββ", 928 | "βρβββββ", 929 | "ββρββββ", 930 | "βββρβββ", 931 | "ββββρββ", 932 | "βββββρβ", 933 | "ββββββρ" 934 | ] 935 | } 936 | } 937 | module.exports = style; --------------------------------------------------------------------------------