├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── README.md ├── build ├── config.js ├── get-plugin.js ├── get-postcss.js ├── rollup.demo.config.js ├── rollup.lib.config.js └── update-version.js ├── docs └── api.md ├── es ├── call-plugin.js ├── index.js ├── plugin.jsx ├── plugins.js └── register.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── demo ├── main.js └── style.css └── styles └── reset.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "loose": true, 8 | "useBuiltIns": false, 9 | "targets": { 10 | "browsers": [ 11 | "> 1%", 12 | "last 2 versions", 13 | "not ie <= 8" 14 | ] 15 | } 16 | } 17 | ] 18 | ], 19 | "plugins": [ 20 | "external-helpers" 21 | ], 22 | "env": { 23 | "development": {}, 24 | "production": {} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | node_modules/ 3 | /build 4 | /lib 5 | /public 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const __DEV__ = (process.env.NODE_ENV || "development") === "development"; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: "babel-eslint", 6 | parserOptions: { 7 | sourceType: "module" 8 | }, 9 | env: { 10 | browser: true 11 | }, 12 | globals: { 13 | console: true 14 | }, 15 | extends: ["standard"], 16 | rules: { 17 | "eol-last": 0, 18 | "comma-dangle": 0, 19 | "no-var": 1, 20 | "no-alert": 1, 21 | "no-unused-vars": __DEV__ ? 1 : 2, 22 | "no-debugger": __DEV__ ? 1 : 2, 23 | "no-console": [ 24 | __DEV__ ? 1 : 2, 25 | { 26 | allow: ["info", "warn", "error"] 27 | } 28 | ], 29 | quotes: [1, "double"], //引号类型 `` "" '' 30 | semi: 0, 31 | "space-before-function-paren": ["error", "never"] 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache/ 3 | .vscode/ 4 | node_modules/ 5 | /lib 6 | /public/static 7 | *yarn* 8 | *.log* 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache/ 3 | .vscode/ 4 | node_modules/ 5 | /build 6 | /public 7 | *yarn* 8 | *.log* 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-plugin-system 2 | 3 | React plugin development system 4 | 5 | ## Describe 6 | 7 | Independent react plugin development, plugin can be quickly inserted and pulled out, plugin async loading mode. 8 | 9 | ## How to use 10 | 11 | ```javascript 12 | // src/index.js 13 | import { register } from "react-plugin-system"; 14 | let pages = require.context("@plugins", true, /\/.*config\.js$/); 15 | pages.keys().map(key => { 16 | let config = pages(key).default; 17 | // reigster plugins 18 | register(config); 19 | return config; 20 | }); 21 | ``` 22 | 23 | ```jsx 24 | // src/pages/*/*.jsx 25 | import React from "react"; 26 | import { callPlugin, Plugin } from "react-plugin-system"; 27 | export defalut class ButtonBox extends React.Component { 28 | constructor(props) { 29 | super(props); 30 | this.state = {}; 31 | } 32 | /** 33 | * add 34 | */ 35 | addHandle = async () => { 36 | // callPlugin 37 | const path = (callPlugin('add')); 38 | const plugin = await import("@plugins/" + path); 39 | (plugin.default)({ a: 1 }); 40 | } 41 | /** 42 | * infoClose 43 | */ 44 | infoCloseHandel = (data) => { 45 | console.log(data) 46 | } 47 | render() { 48 | const path = (callPlugin('info')); 49 | return
50 | 51 | { return import("@plugins/" + path) }} onClose={this.infoCloseHandel} /> 52 |
53 | } 54 | } 55 | ``` 56 | 57 | ## plugin demo 58 | 59 | ### Add plugin 60 | 61 | ```js 62 | // src/plugins/add/config.js 63 | export default { 64 | id: "add", 65 | index: "add/index.js" 66 | }; 67 | ``` 68 | 69 | ```js 70 | // scr/plugins/add/index.js 71 | import { message } from "antd"; 72 | export default function(config = {}) { 73 | const { calllBack = () => {} } = config; 74 | message.success("Add success!"); 75 | calllBack(); 76 | } 77 | ``` 78 | 79 | ### Info plugin 80 | 81 | ```js 82 | // src/plugins/info/config.js 83 | export default { 84 | id: "info", 85 | index: "info/index.jsx" 86 | }; 87 | ``` 88 | 89 | ```js 90 | // scr/plugins/info/index.jsx 91 | import React from "react"; 92 | import { Button, notification } from "antd"; 93 | export default class Info extends React.Component { 94 | constructor(props) { 95 | super(props); 96 | this.state = {}; 97 | } 98 | clickHandle = () => { 99 | const { onClose = () => {} } = this.props; 100 | notification["info"]({ 101 | message: "Project info", 102 | description: "Here is the notice!", 103 | onClose: () => { 104 | onClose("User Close"); 105 | } 106 | }); 107 | }; 108 | render() { 109 | return ( 110 | 113 | ); 114 | } 115 | } 116 | ``` 117 | 118 | ## Notice 119 | 120 | The system needs to add "@plugins" alias. 121 | 122 | ```js 123 | // webpack.config.js 124 | { 125 | alias: { 126 | "react-native": "react-native-web", 127 | "@plugins": path.resolve("./src/plugins"), 128 | } 129 | } 130 | ``` 131 | 132 | ### 133 | 134 | ## License 135 | 136 | Licensed under the Apache License, Version 2.0 137 | () 138 | -------------------------------------------------------------------------------- /build/config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | const __DEV__ = (process.env.NODE_ENV || 'development') === 'development'; 4 | 5 | export default { 6 | dev: __DEV__, 7 | live: true, 8 | eslint: true, 9 | extract: true, 10 | serve: { 11 | base: 'public', 12 | port: 5000 13 | }, 14 | alias: { 15 | '@es': path.resolve('es'), 16 | '@styles': path.resolve('src/styles') 17 | }, 18 | px2rem: { 19 | use: false, 20 | unit: 16 21 | }, 22 | cssModules: { 23 | global: ['node_modules', 'src/styles'] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /build/get-plugin.js: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | import getPostcss from './get-postcss'; 3 | 4 | import progress from 'rollup-plugin-progress'; 5 | import replace from 'rollup-plugin-replace'; 6 | import alias from 'rollup-plugin-alias'; 7 | import json from 'rollup-plugin-json'; 8 | import resolve from 'rollup-plugin-node-resolve'; 9 | import commonjs from 'rollup-plugin-commonjs'; 10 | import eslint from 'rollup-plugin-eslint'; 11 | import babel from 'rollup-plugin-buble'; 12 | import uglify from 'rollup-plugin-uglify'; 13 | import filesize from 'rollup-plugin-filesize'; 14 | import postcss from 'rollup-plugin-postcss'; 15 | import serve from 'rollup-plugin-serve'; 16 | import livereload from 'rollup-plugin-livereload'; 17 | 18 | const plugins = { 19 | progress (opt) { 20 | return progress({ 21 | clearLine: opt.clear || false 22 | }); 23 | }, 24 | replace () { 25 | return replace({ 26 | exclude: 'node_modules/**', 27 | NODE_ENV: JSON.stringify(config.dev ? 'development' : 'production') 28 | }); 29 | }, 30 | alias () { 31 | return alias(config.alias); 32 | }, 33 | json () { 34 | return json({ 35 | exclude: ['node_modules/**'], 36 | preferConst: true 37 | }); 38 | }, 39 | resolve () { 40 | return resolve(); 41 | }, 42 | commonjs () { 43 | return commonjs(); 44 | }, 45 | eslint () { 46 | if (!config.eslint) return; 47 | 48 | return eslint({ 49 | include: ['src/**/*.js', 'es/**/*.js'] 50 | }); 51 | }, 52 | babel () { 53 | return babel({ 54 | include: ['src/**', 'es/**', 'node_modules/**/es/**'] 55 | }); 56 | }, 57 | uglify (opt) { 58 | return uglify(opt); 59 | }, 60 | filesize () { 61 | return filesize(); 62 | }, 63 | postcss (opt) { 64 | if (!config.extract && !opt.force) return; 65 | 66 | return postcss(getPostcss(opt)); 67 | }, 68 | serve () { 69 | return serve({ 70 | contentBase: config.serve.base, 71 | port: config.serve.port, 72 | host: '127.0.0.1', 73 | historyApiFallback: true 74 | }); 75 | }, 76 | livereload () { 77 | if (!config.live) return; 78 | 79 | return livereload( 80 | config.serve.base 81 | ); 82 | } 83 | }; 84 | 85 | export default function getPlugin (name, opt = {}) { 86 | return plugins[name](opt); 87 | } -------------------------------------------------------------------------------- /build/get-postcss.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import config from './config'; 3 | 4 | export default function getPostcss ({ 5 | extract, 6 | minify 7 | }) { 8 | const cssExportMap = {}; 9 | 10 | return { 11 | sourceMap: config.dev, 12 | extensions: ['.css', '.mcss'], 13 | extract, 14 | getExport (id) { 15 | return cssExportMap[id] || {}; 16 | }, 17 | plugins: [ 18 | require('postcss-import')({ 19 | path: [ 20 | path.resolve('node_modules') 21 | ] 22 | }), 23 | require('postcss-mixins')(), 24 | require('postcss-advanced-variables')(), 25 | require('postcss-color-function')(), 26 | require('postcss-nested')(), 27 | require('postcss-extend')(), 28 | require('postcss-calc')({ 29 | mediaQueries: true, 30 | selectors: false 31 | }), 32 | config.px2rem.use && require('postcss-px2rem')({ 33 | remUnit: config.px2rem.unit || 16, 34 | remPrecision: 5, 35 | keepComment: 'no2rem' 36 | }), 37 | require('autoprefixer')({ 38 | browsers: [ 39 | 'ie >= 9', 40 | 'ie_mob >= 10', 41 | 'ff >= 30', 42 | 'chrome >= 34', 43 | 'safari >= 7', 44 | 'opera >= 23', 45 | 'ios >= 7', 46 | 'android >= 4.4', 47 | 'bb >= 10' 48 | ] 49 | }), 50 | require('postcss-modules')({ 51 | generateScopedName: '[local]___[hash:base64:8]', 52 | globalModulePaths: config.cssModules.global, 53 | getJSON (id, exportTokens) { 54 | cssExportMap[id] = exportTokens; 55 | } 56 | }), 57 | (!config.dev && minify) && require('postcss-csso')() 58 | ].filter(p => p) 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /build/rollup.demo.config.js: -------------------------------------------------------------------------------- 1 | import config from './config'; 2 | import getPlugin from './get-plugin'; 3 | 4 | export default { 5 | input: 'src/demo/main.js', 6 | output: { 7 | file: 'public/static/bundle.js', 8 | format: 'iife', 9 | sourcemap: config.dev 10 | }, 11 | plugins: [ 12 | getPlugin('progress', { 13 | clear: false 14 | }), 15 | getPlugin('replace'), 16 | getPlugin('eslint'), 17 | getPlugin('alias'), 18 | getPlugin('postcss', { 19 | extract: 'public/static/style.css', 20 | minify: !config.dev, 21 | force: true 22 | }), 23 | getPlugin('json'), 24 | getPlugin('resolve'), 25 | getPlugin('commonjs'), 26 | getPlugin('babel'), 27 | config.dev && getPlugin('serve'), 28 | config.dev && getPlugin('livereload'), 29 | !config.dev && getPlugin('uglify'), 30 | !config.dev && getPlugin('filesize') 31 | ].filter(p => p) 32 | }; 33 | -------------------------------------------------------------------------------- /build/rollup.lib.config.js: -------------------------------------------------------------------------------- 1 | import getPlugin from './get-plugin'; 2 | import { name } from '../package.json'; 3 | 4 | const entry = 'es/index.js'; 5 | const moduleName = name.replace(/-(\w)/g, ($, $1) => $1.toUpperCase()); 6 | 7 | export default [{ 8 | input: entry, 9 | name: moduleName, 10 | output: { 11 | file: 'lib/index.js', 12 | format: 'umd', 13 | exports: 'named', 14 | sourcemap: false 15 | }, 16 | plugins: [ 17 | getPlugin('progress', { 18 | clear: true 19 | }), 20 | getPlugin('replace'), 21 | getPlugin('eslint'), 22 | getPlugin('alias'), 23 | getPlugin('postcss', { 24 | extract: 'lib/style.css', 25 | minify: false 26 | }), 27 | getPlugin('json'), 28 | getPlugin('resolve'), 29 | getPlugin('commonjs'), 30 | getPlugin('babel'), 31 | getPlugin('filesize') 32 | ].filter(p => p) 33 | }, { 34 | input: entry, 35 | name: moduleName, 36 | output: { 37 | file: 'lib/index.min.js', 38 | format: 'umd', 39 | exports: 'named', 40 | sourcemap: false 41 | }, 42 | plugins: [ 43 | getPlugin('progress', { 44 | clear: true 45 | }), 46 | getPlugin('replace'), 47 | getPlugin('eslint'), 48 | getPlugin('alias'), 49 | getPlugin('postcss', { 50 | extract: 'lib/style.min.css', 51 | minify: true 52 | }), 53 | getPlugin('json'), 54 | getPlugin('resolve'), 55 | getPlugin('commonjs'), 56 | getPlugin('babel'), 57 | getPlugin('uglify'), 58 | getPlugin('filesize') 59 | ].filter(p => p) 60 | }]; -------------------------------------------------------------------------------- /build/update-version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const pkg = require('../package.json'); 3 | 4 | const argvs = process.argv.slice(2); 5 | const __LARGE__ = argvs.indexOf('l') !== -1; 6 | const __MIDDLE__ = argvs.indexOf('m') !== -1; 7 | 8 | let vs = pkg.version.split('.'); 9 | 10 | if (__LARGE__) { 11 | vs[0]++; 12 | vs[1] = 0; 13 | vs[2] = 0; 14 | } else if (__MIDDLE__) { 15 | vs[1]++; 16 | vs[2] = 0; 17 | } else { 18 | vs[2]++; 19 | } 20 | 21 | pkg.version = vs.join('.'); 22 | 23 | fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2), { encoding: 'utf8' }); 24 | console.log(' version: ' + pkg.version + '\n'); 25 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # API -------------------------------------------------------------------------------- /es/call-plugin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: yuanchengyong 3 | * @Date: 2020-01-15 13:55:40 4 | * @Last Modified by: zyycy_love@126.com 5 | * @Last Modified time: 2020-01-16 14:39:29 6 | * @Des 调用插件 7 | */ 8 | import plugins from "./plugins"; 9 | export default function(id = "", config = {}) { 10 | let plugin = {}; 11 | for (let i = 0; i < plugins.length; i++) { 12 | if (id === plugins[i].id) { 13 | plugin = plugins[i]; 14 | break; 15 | } 16 | } 17 | if (!plugin.id) { 18 | console.warn("The plugin you called does not exist!"); 19 | return false; 20 | } 21 | // console.log(plugin); 22 | // console.log(path.join("./sssss")); 23 | let path = plugin.index.replace(/^(.\/)*/g, ""); 24 | return path; 25 | // let e = await import("@plugins/" + path); 26 | // e.default(config); 27 | } 28 | -------------------------------------------------------------------------------- /es/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: yuanchengyong 3 | * @Date: 2020-01-15 09:37:38 4 | * @Last Modified by: zyycy_love@126.com 5 | * @Last Modified time: 2020-01-16 16:15:41 6 | * @Des 插件系统核心 7 | */ 8 | import plugins from "./plugins"; 9 | // import Plugin from "./plugin.jsx"; 10 | import { register } from "./register"; 11 | import callPlugin from "./call-plugin"; 12 | import Plugin from "./plugin.jsx"; 13 | export { 14 | plugins, // 插件列表 15 | callPlugin, // 调用插件 16 | register, // 单插件注册 17 | Plugin // 插件组件 18 | }; 19 | -------------------------------------------------------------------------------- /es/plugin.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: yuanchengyong 3 | * @Date: 2020-01-15 17:11:34 4 | * @Last Modified by: zyycy_love@126.com 5 | * @Last Modified time: 2020-01-16 16:15:16 6 | */ 7 | import React, { Component } from 'react'; 8 | export default class extends Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | component: null 13 | } 14 | } 15 | componentDidMount() { 16 | let { importComponent } = this.props; 17 | importComponent() 18 | .then(cmp => { 19 | this.setState({ component: cmp.default }); 20 | }); 21 | } 22 | render() { 23 | let props = {}; 24 | for (let item in this.props) { 25 | if (item !== 'importComponent') { 26 | props[item] = this.props[item]; 27 | } 28 | } 29 | const C = this.state.component; 30 | return C ? : null; 31 | } 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /es/plugins.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: yuanchengyong 3 | * @Date: 2020-01-15 10:12:19 4 | * @Last Modified by: zyycy_love@126.com 5 | * @Last Modified time: 2020-01-16 14:28:59 6 | * @Des 插件暂存列表 7 | */ 8 | const plugins = []; 9 | export default plugins; 10 | -------------------------------------------------------------------------------- /es/register.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: yuanchengyong 3 | * @Date: 2020-01-15 09:48:43 4 | * @Last Modified by: zyycy_love@126.com 5 | * @Last Modified time: 2020-01-16 14:31:41 6 | * @Des 插件注册 7 | */ 8 | import plugins from "./plugins"; 9 | 10 | const findIndex = (array = [], fun = () => {}) => { 11 | let index = -1; 12 | for (let i = 0; i < array.length; i++) { 13 | if (fun(array[i])) { 14 | index = i; 15 | break; 16 | } 17 | } 18 | return index; 19 | }; 20 | /** 21 | * 单插件注册方法 22 | * @param {Object} config 插件配置 23 | */ 24 | const register = config => { 25 | if (Object.prototype.toString.call(config) !== "[object Object]") { 26 | console.warn("Plugin config does not exist!"); 27 | return false; 28 | } 29 | const id = config.id; 30 | if (!id) { 31 | console.warn("Id of plugin config is required!"); 32 | return false; 33 | } 34 | const index = findIndex(plugins, function(o) { 35 | return o.id === id; 36 | }); 37 | if (index > -1) { 38 | console.warn(`Plugin id "${id}" already exist!`); 39 | return false; 40 | } 41 | plugins.push(config); 42 | }; 43 | export { register }; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-plugin-system", 3 | "version": "0.1.6", 4 | "description": "react-plugin-system project build by rollup-cli.", 5 | "main": "lib/index.min.js", 6 | "module": "lib/index.js", 7 | "scripts": { 8 | "dev": "./node_modules/.bin/rollup -c build/rollup.demo.config.js --watch", 9 | "demo": "./node_modules/.bin/rimraf public/static && NODE_ENV=production ./node_modules/.bin/rollup -c build/rollup.demo.config.js", 10 | "lib": "./node_modules/.bin/rimraf lib && NODE_ENV=production ./node_modules/.bin/rollup -c build/rollup.lib.config.js", 11 | "v": "node build/update-version.js", 12 | "pub": "npm run lib && npm publish" 13 | }, 14 | "keywords": [ 15 | "react plugin" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/yuancy-code/react-plugin-system.git" 20 | }, 21 | "author": "z137168075 ", 22 | "license": "MIT", 23 | "dependencies": {}, 24 | "devDependencies": { 25 | "autoprefixer": "^7.2.3", 26 | "babel-core": "^6.26.0", 27 | "babel-eslint": "^8.0.3", 28 | "babel-plugin-external-helpers": "^6.22.0", 29 | "babel-preset-env": "^1.6.1", 30 | "eslint": "^4.13.1", 31 | "eslint-config-standard": "^11.0.0-beta.0", 32 | "eslint-friendly-formatter": "^3.0.0", 33 | "eslint-plugin-import": "^2.8.0", 34 | "eslint-plugin-node": "^5.2.1", 35 | "eslint-plugin-promise": "^3.6.0", 36 | "eslint-plugin-standard": "^3.0.1", 37 | "postcss-advanced-variables": "^1.2.2", 38 | "postcss-calc": "^6.0.1", 39 | "postcss-color-function": "^4.0.1", 40 | "postcss-csso": "^3.0.0", 41 | "postcss-extend": "^1.0.5", 42 | "postcss-import": "^11.0.0", 43 | "postcss-loader": "^2.0.9", 44 | "postcss-mixins": "^6.2.0", 45 | "postcss-modules": "^1.1.0", 46 | "postcss-nested": "^3.0.0", 47 | "postcss-px2rem": "^0.3.0", 48 | "rimraf": "^2.6.2", 49 | "rollup": "^0.52.3", 50 | "rollup-plugin-alias": "^1.4.0", 51 | "rollup-plugin-buble": "^0.18.0", 52 | "rollup-plugin-commonjs": "^8.2.6", 53 | "rollup-plugin-eslint": "^4.0.0", 54 | "rollup-plugin-filesize": "^1.5.0", 55 | "rollup-plugin-json": "^2.3.0", 56 | "rollup-plugin-livereload": "^0.6.0", 57 | "rollup-plugin-node-resolve": "^3.0.0", 58 | "rollup-plugin-postcss": "^0.5.5", 59 | "rollup-plugin-progress": "^0.4.0", 60 | "rollup-plugin-replace": "^2.0.0", 61 | "rollup-plugin-serve": "^0.4.2", 62 | "rollup-plugin-uglify": "^2.0.1", 63 | "rollup-watch": "^4.3.1" 64 | } 65 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuancy-code/react-plugin-system/d7a70d48efba3439a0c59b590b631e5badec6620/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | rollup-cli 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/demo/main.js: -------------------------------------------------------------------------------- 1 | import lib from '@es/index' 2 | 3 | import resetCss from '@styles/reset.css' 4 | import mainCss from './style.css' 5 | 6 | console.info(lib) 7 | console.info(resetCss) 8 | console.info(mainCss) 9 | -------------------------------------------------------------------------------- /src/demo/style.css: -------------------------------------------------------------------------------- 1 | .test { 2 | background-color: red; 3 | } -------------------------------------------------------------------------------- /src/styles/reset.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 16px; /* no2rem */ 3 | box-sizing: border-box; 4 | overflow-y: scroll; 5 | -webkit-tap-highlight-color: transparent; 6 | -webkit-text-size-adjust: 100%; 7 | -ms-text-size-adjust: 100%; 8 | } 9 | 10 | *, 11 | *:before, 12 | *:after { 13 | box-sizing: inherit; 14 | } 15 | 16 | body { 17 | font-family: BlinkMacSystemFont, -apple-system, "Helvetica Neue", "Helvetica", "Arial", sans-serif; 18 | font-size: 14px; 19 | line-height: 1.5; 20 | color: #4a4a4a; 21 | background-color: #fff; 22 | margin: 0 auto; 23 | } 24 | 25 | h1, 26 | h2, 27 | h3, 28 | h4, 29 | h5, 30 | h6 { 31 | margin: 0; 32 | font-weight: 500; 33 | line-height: 1.5; 34 | } 35 | 36 | p, 37 | hr, 38 | table, 39 | ul, 40 | ol { 41 | margin: 0; 42 | padding: 0; 43 | } 44 | 45 | button, 46 | input, 47 | select, 48 | textarea { 49 | border: 0; 50 | outline: 0; 51 | margin: 0; 52 | padding: 0; 53 | font: inherit; 54 | color: inherit; 55 | line-height: normal; 56 | } 57 | 58 | table { 59 | border-collapse: collapse; 60 | border-spacing: 0; 61 | } 62 | 63 | td, 64 | th { 65 | padding: 0; 66 | } 67 | 68 | ul { 69 | list-style: none; 70 | } 71 | 72 | img { 73 | border: 0; 74 | max-width: 100%; 75 | vertical-align: middle; 76 | } 77 | 78 | a { 79 | color: #3273dc; 80 | text-decoration: none; 81 | } 82 | 83 | a:hover { 84 | color: #276cda; 85 | } --------------------------------------------------------------------------------