├── .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 | }
--------------------------------------------------------------------------------