webpack+react环境搭建,不必要再使用create-react-app来创建,随意配置更方便

首先是配置文件package.json,里边包括所用到的babel以及关于react的一些依赖包。

{
  "name": "service",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@antv/g6": "^1.2.1",
    "@babel/core": "^7.2.0",
    "@babel/plugin-proposal-class-properties": "^7.2.1",
    "@babel/plugin-proposal-decorators": "^7.2.0",
    "@babel/plugin-proposal-export-default-from": "^7.2.0",
    "@babel/plugin-proposal-export-namespace-from": "^7.2.0",
    "@babel/plugin-proposal-object-rest-spread": "^7.2.0",
    "@babel/plugin-transform-member-expression-literals": "^7.2.0",
    "@babel/plugin-transform-object-assign": "^7.2.0",
    "@babel/plugin-transform-property-literals": "^7.2.0",
    "@babel/plugin-transform-runtime": "^7.2.0",
    "@babel/plugin-transform-spread": "^7.2.0",
    "@babel/plugin-transform-template-literals": "^7.2.0",
    "@babel/preset-env": "^7.2.0",
    "@babel/preset-react": "^7.0.0",
    "@babel/preset-typescript": "^7.1.0",
    "@types/react": "^16.8.22",
    "@types/react-dom": "^16.8.4",
    "antd": "^3.13.6",
    "autoprefixer": "^9.4.2",
    "awesome-typescript-loader": "^5.2.1",
    "axios": "^0.19.0",
    "babel-core": "^7.0.0-0",
    "babel-eslint": "^10.0.2",
    "babel-jest": "^24.0.0",
    "babel-loader": "^8.0.4",
    "babel-plugin-add-module-exports": "^1.0.0",
    "babel-plugin-dynamic-import-webpack": "^1.1.0",
    "babel-plugin-import": "^1.11.0",
    "babel-plugin-inline-import-data-uri": "^1.0.1",
    "babel-polyfill": "^6.26.0",
    "babel-preset-stage-2": "^6.24.1",
    "body-parser": "^1.19.0",
    "classnames": "^2.2.5","core-decorators": "^0.20.0",
    "css-loader": "^2.1.1",
    "echarts": "^3.6.1",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^4.2.0",
    "eslint-plugin-babel": "^5.3.0",
    "eslint-plugin-jest": "^22.5.1",
    "eslint-plugin-markdown": "^1.0.0",
    "file-loader": "^3.0.1",
    "fs-extra": "^7.0.1","js-base64": "^2.5.1",
    "js-export-excel": "^1.1.2",
    "jstz": "^2.1.1",
    "jszip": "^3.1.5",
    "moment": "^2.22.2",
    "object-assign": "4.1.1",
    "optimize-css-assets-webpack-plugin": "^5.0.1",
    "pm2": "^3.5.0",
    "promise": "8.0.1",
    "prop-types": "^15.6.0",
    "rc-animate": "^2.4.1",
    "rc-queue-anim": "^1.2.3",
    "react": "^16.8.2",
    "react-dom": "^16.8.2",
    "react-intl": "^2.8.0",
    "react-redux": "^5.0.6",
    "react-router": "^3.0.0",
    "react-router-dom": "^4.0.0",
    "react-router-redux": "^4.0.8",
    "react-table": "^6.8.0",
    "redux": "^3.6.0",
    "redux-thunk": "^2.2.0","sockjs-client": "^1.3.0",
    "ts-loader": "^6.0.4",
    "typescript": "^3.5.2",
    "uglifyjs-webpack-plugin": "^2.1.3",
    "url-loader": "^1.1.2",
    "webstomp-client": "^1.2.5",
    "whatwg-fetch": "2.0.3",
    "xlsx": "^0.11.10",
  },
  "devDependencies": {
    "@babel/preset-es2015": "^7.0.0-beta.53",
    "case-sensitive-paths-webpack-plugin": "2.1.1",
    "chalk": "^2.4.2",
    "clean-webpack-plugin": "0.1.19",
    "copy-to-clipboard": "^3.0.8",
    "copy-webpack-plugin": "^5.0.3",
    "cross-env": "^5.1.6",
    "cryptojs": "^2.5.3",
    "css-modules-require-hook": "^4.2.2",
    "dekko": "^0.2.1",
    "dotenv": "4.0.0",
    "ejs": "^2.6.1",
    "eslint": "^4.4.1",
    "eslint-config-react-app": "^2.0.1",
    "eslint-config-standard": "^12.0.0",
    "eslint-config-standard-react": "^7.0.2",
    "eslint-loader": "1.9.0",
    "eslint-plugin-flowtype": "2.35.0",
    "eslint-plugin-html": "^5.0.3",
    "eslint-plugin-import": "^2.7.0",
    "eslint-plugin-jsx-a11y": "5.1.1",
    "eslint-plugin-node": "^9.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-react": "^7.1.0",
    "eslint-plugin-standard": "^4.0.0",
    "expose-loader": "^0.7.4",
    "express": "^4.16.4",
    "extract-text-webpack-plugin": "3.0.0",
    "fs": "0.0.1-security",
    "happypack": "^5.0.1",
    "html-res-webpack-plugin": "^4.0.0",
    "html-webpack-plugin": "^3.2.0",
    "http-proxy-middleware": "^0.18.0","jest": "20.0.4",
    "lebab": "^2.7.7",
    "less": "^3.9.0",
    "less-loader": "^5.0.0",
    "less-middleware": "^2.2.1",
    "mini-css-extract-plugin": "^0.6.0",
    "node-less": "^1.0.0",
    "node-notifier": "^5.2.1",
    "nodemailer": "^6.2.1",
    "nodemailer-smtp-transport": "^2.7.4",
    "notifier": "^0.2.0",
    "postcss-flexbugs-fixes": "3.2.0",
    "postcss-loader": "2.0.6",
    "rc-tween-one": "^1.4.5",
    "react-cropper": "^1.0.1",
    "react-dev-utils": "^4.2.3",
    "react-dnd": "^2.6.0",
    "react-dnd-html5-backend": "^2.6.0",
    "react-syntax-highlighter": "^5.7.0",
    "react.proptypes": "^2.0.0",
    "redux-devtools": "^3.4.0",
    "redux-devtools-dock-monitor": "^1.1.2",
    "redux-devtools-log-monitor": "^1.3.0",
    "reqwest": "^2.0.5",
    "reselect": "^3.0.1",
    "style-loader": "0.18.2",
    "sw-precache-webpack-plugin": "0.11.4",
    "webpack": "^4.30.0",
    "webpack-bundle-analyzer": "^3.3.2",
    "webpack-cli": "^3.3.2",
    "webpack-dev-server": "^3.1.14",
    "webpack-manifest-plugin": "1.2.1",
    "webpackbar": "^3.2.0"
  },
  "scripts": {
    "start": "node --max_old_space_size=8096 ./config/start.js","build": "node --max_old_space_size=8096 ./config/prod.js",
    "test": "node --max_old_space_size=8096 ./config/test.js",
    "pro": "node --max_old_space_size=8096 ./config/service.pro.js",
    "production": "node ./config/production.js",
    "development": "node ./config/devPro.js"
  },
  "eslintConfig": {
    "extends": "react-app",
    "env": {
      "browser": true,
      "node": true
    }
  }
}

 

接下来是开发环境的配置,创建一个config文件夹,创建webpack.config.js

const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackBar = require('webpackbar');
const HappyPack = require('happypack');
const autoprefixer = require('autoprefixer');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
    output: {
        filename: 'static/js/[name].[hash]' + new Date().getTime() + '.js',
        path: path.resolve(__dirname, '../public'),
        chunkFilename: 'static/js/[name].[hash].bundle.js',
        publicPath: '/'
    },
    module: {
        rules: [
            {
                test: /\.js?$/,
                exclude: [path.resolve(__dirname, 'node_modules')],
                loader: 'happypack/loader?id=js_babel'
            },
            {
                test: /\.tsx?$/,
                use: {
                    loader: 'ts-loader',
                }
            },
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true,
                        },
                    },
                    {
                        loader: 'postcss-loader',
                        options: Object.assign({}, autoprefixer({ overrideBrowserslist: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 9'] }), { sourceMap: true }),
                    },
                ],
            },
            {
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true,
                        },
                    },
                    {
                        loader: 'postcss-loader',
                        options: Object.assign({}, autoprefixer({ overrideBrowserslist: ['last 2 versions', 'Firefox ESR', '> 1%', 'ie >= 9'] }), { sourceMap: true }),
                    },
                    {
                        loader: 'less-loader',
                        options: {
                            javascriptEnabled: true,
                            sourceMap: true,
                        },
                    },
                ],
            },
            {
                test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/,
                loader: 'url-loader?limit=8192&name=images/[hash:8].[name].[ext]'
            }
        ],
    },
    externals: {
        react: 'React',
        'react-dom': 'ReactDOM',
        jquery: 'jQuery'
    },
    optimization: {
        splitChunks: {
            chunks: 'async',
            minSize: 30000,
            maxSize: 0,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    },
    resolve: {
        extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'],
        alias: {
            UTIL_: path.resolve(__dirname, '../src/util.js'),
            common_: path.resolve(__dirname, '..src/components/modelCommon/common.js'),
            modelCommon: path.resolve(__dirname, '..src/components/modelCommon'),
        }
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "static/css/index.[hash].css"
        }),new HappyPack({
            id: 'js_babel',
            threadPool: happyThreadPool,
            loaders: [
                {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            "@babel/preset-env",
                            "@babel/preset-react",
                        ]
                    }
                },
                {
                    loader: 'eslint-loader'
                }
            ],
            verbose: true
        }),
        new HtmlWebpackPlugin({
            inject: 'body',
            template: path.resolve(__dirname, '../public/index.1.html')
        })
    ]
}

创建start.js

process.env.NODE_ENV = 'development';
process.env.BABEL_ENV = 'development';

const util = require('./util')
const config = require('./webpack.config.config.js')
const { startApp } = util;

startApp({
    proxy: 'http://www.xx.com',
    config,
    mode:'development',
    port:3000,
})

startApp方法

function startApp({ proxy, config,mode ,port}) {
    const inoutName = 'main';
    config.devtool = 'cheap-module-eval-source-map';
    config.entry = {
        [inoutName]: path.resolve(__dirname, '../src/index.js'),
        webpackHotDevClient: require.resolve('react-dev-utils/webpackHotDevClient')
    };
    config.mode = mode;
    config.plugins.push(new webpack.NamedModulesPlugin());
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
    const webpackConfig = webpack(config);
    const devServer = new WebpackDevServer(webpackConfig, {
        contentBase: path.resolve(__dirname, '../public'),
        compress: true,
        stats: {
            warnings: true,
            errors: true,
            children: false
        },
        historyApiFallback: true,
        clientLogLevel: 'none',
        proxy: {
            '/userModel': {
                target:proxy, 
                changeOrigin: true,
                secure: false,
            },
            '/': {
                target: proxy,
                changeOrigin: true,
                bypass: function (req) {
                    if (/\.(gif|jpg|png|woff|svg|eot|ttf|js|jsx|json|css|pdf)$/.test(req.url)) {
                        return req.url;
                    }
                    //return req.url;
                }
            }
        }
    });

    devServer.listen(port, process.env.HOST || '0.0.0.0', (err) => {
        if (err) {
            return console.log(err);
        }
        clearConsole();
        console.log(chalk.rgb(212, 133, 184)('⚠️ 启动成功:'), chalk.bgGreen('http://' + host() +':'+ port), '\n');
        openBrowser('http://' + host() +':'+ port)
    });
}

开发环境基本上搭建完成。开始搭建生产环境

process.env.NODE_ENV = 'production';
process.env.BABEL_ENV = 'production';

const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
const fss = require('fs-extra');
const webpackConfig = require('./webpack.config.config.js');
const chalk = require('chalk');
const notifier = require('node-notifier');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const inoutName = 'main';
const inputJs = path.resolve(__dirname, '../src/index.js');
const clearConsole = require('react-dev-utils/clearConsole');
createFolder();
clearConsole();
webpackConfig.devtool = false;
webpackConfig.entry = {
    [`${inoutName}`]: inputJs
}
webpackConfig.mode = 'production';
webpackConfig.plugins.push(new webpack.optimize.ModuleConcatenationPlugin());
webpackConfig.plugins.push(new webpack.LoaderOptionsPlugin({
    minimize: true,
}));

fss.emptyDirSync(path.resolve(__dirname, '../build'))
fss.emptyDirSync(path.resolve(__dirname, '../public/static'))

webpack(webpackConfig).run((err, options) => {
    clearConsole();
    if (err) {
        console.error('❌ 错误信息:', err);
        return;
    }
    if (err || options.hasErrors()) {
        if (options.compilation.warnings) {
            options.compilation.warnings.forEach(item => {
                console.log(chalk.green('⚠️ 警告:', item.message, '')), '\n');
            })
        }
        
        console.log(chalk.yellow(options.compilation.errors[0].error.message), '\n');
        console.log(chalk.red('❌ 错误信息:', options.compilation.errors));
        return;
    }
    notice('ServiceBot: 打包成功!')
    const { startTime, endTime } = options;
    const times = (endTime - startTime) / 1e3 / 60;
    if (options.compilation.warnings) {
        options.compilation.warnings.forEach(item => {
            console.log(chalk.green('⚠️ 警告:', item.message)), '\n');
        })
    }
    console.log(chalk.yellow('        -                                         '));
    console.log(chalk.yellow('       | |                           _          _ '));
    console.log(chalk.yellow('       | |                          | |        (_)'));
    console.log(chalk.yellow(' _____ | |__   _____   ____    ____ | |  _____  _ '));
    console.log(chalk.yellow('(___  )|  _  | (____| |  _  | / _  || | | ___ || |'));
    console.log(chalk.yellow(' / __/ | | | |/ ___ | | | | |( (_| || | | ____|| |'));
    console.log(chalk.yellow('(_____)|_| |_||_____| |_| |_| |___ ||_) |_____)|_|'));
    console.log(chalk.yellow('                             (_____|              '), '\n');
    console.log(chalk.rgb(212, 133, 184)('⚠️ 开始时间:'), chalk.bgGreen(isCurrentTime(new Date(startTime))), '\n');
    console.log(chalk.rgb(212, 133, 184)('⚠️ 结束时间:'), chalk.bgGreen(isCurrentTime(new Date(endTime))), '\n');
    console.log(chalk.rgb(212, 133, 184)('✅ 总共用时:'), chalk.yellowBright(`${parseFloat(times).toFixed(2)}分钟`), '\n');
    copyFolder(path.resolve(__dirname, '../public'), path.resolve(__dirname, '../build'));
    fss.emptyDirSync(path.resolve(__dirname, '../public/static'))
})

function notice(message) {
    notifier.notify({
        title: 'ServiceBot',
        message,
        icon: path.join(__dirname, '../public/img/8.jpg'),
        sound: true,
        wait: true
    });
}




notifier.on('click', function (notifierObject, options) {
    // Triggers if `wait: true` and user clicks notification
});

notifier.on('timeout', function (notifierObject, options) {
    notice()
});



function copyFolder(src, tar) {
    fs.readdirSync(src).forEach(path => {
        const newSrc = `${src}/${path}`;
        const newTar = `${tar}/${path}`
        const st = fs.statSync(newSrc);
        if (st.isDirectory()) {
            fs.mkdirSync(newTar)
            return copyFolder(newSrc, newTar)
        }
        if (st.isFile()) {
            fs.writeFileSync(newTar, fs.readFileSync(newSrc))
        }
    })
}

function createFolder() {
    if (!fs.existsSync(path.resolve(__dirname, '../build'))) {
        fs.mkdirSync(path.resolve(__dirname, '../build'))
    }
}

function isCurrentTime(time) {
    const y = time.getFullYear();
    const month = time.getMonth() + 1;
    const hour = time.getHours();
    const min = time.getMinutes();
    const sec = time.getSeconds();
    const day = time.getDate();
    const m = month < 10 ? `0${month}` : month;
    const h = hour < 10 ? `0${hour}` : hour;
    const mins = min < 10 ? `0${min}` : min;
    const s = sec < 10 ? `0${sec}` : sec;
    const d = day < 10 ? `0${day}` : day;
    return `${y}-${m}-${d} ${h}:${mins}:${s}`
}

生产环境基本搭建完成,如果想直接启动本地生产项目,可以使用express代理一个生产环境

最后需要在public目录下增加html页面

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="Access-Control-Allow-Origin" content="*">
  <link rel="shortcut icon" href="favicon.ico">
  
  
  <title>Service</title>
  <style>
    .loader {
      position: absolute;
      top: 50%;
      left: 40%;
      margin-left: 10%;
      transform: translate3d(-50%, -50%, 0);
      font-size: 40px;
      font-weight: bold;
    }

    .loader span {
      animation: slide 1s infinite;
      color: #ccc;
    }

    .loader span:nth-child(1) {
      animation-delay: .1s
    }

    .loader span:nth-child(2) {
      animation-delay: .2s
    }

    .loader span:nth-child(3) {
      animation-delay: .3s
    }

    .loader span:nth-child(4) {
      animation-delay: .4s
    }

    .loader span:nth-child(5) {
      animation-delay: .5s
    }

    @-webkit-keyframes slide {
      0% {
        color: #ccc
      }

      50% {
        opacity: .3;
        color: #666
      }
    }

    @keyframes slide {
      0% {
        color: #ccc
      }

      50% {
        opacity: .3;
        color: #666
      }
    }
  </style>
</head>

<body id="body">
  <div id="root">
    <div class="loader">
      <span>S</span>
      <span>e</span>
      <span>r</span>
      <span>v</span>
      <span>i</span>
      <span>c</span>
      <span>e</span>
    </div>
  </div>
  <script src="/data/react.production.min.js"></script>
  <script src="/data/react-dom.production.min.js"></script>
</body>

</html>

基本结构搭建完成,现在是处理index.js入口文件。

会用到react-redux 做了初步的国际化方案

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import Routers from './routerMenu/preRouter';
import Reducers from './routerMenu/rootReducers';
import enhancer from './routerMenu/configureStore'
import { IntlProvider, addLocaleData } from 'react-intl';
import { isEnUS } from './util'
import { LocaleProvider } from 'antd';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import en_US from 'antd/lib/locale-provider/en_US';
import 'moment/locale/zh-cn';
import 'babel-polyfill';
import 'whatwg-fetch';
import zhCN from './zh-CN';
import enUS from './en-US';

let store = createStore(Reducers, {}, enhancer);
const { useState, useEffect } = React;
const { location, localStorage, addEventListener } = window;
function App({ onChange }) {
    const appLocale = isEnUS(location.hash) ? enUS : zhCN
    addLocaleData(appLocale.data);
    const [AL, setLocale] = useState(appLocale);
    const [key, setKey] = useState(0);
    const callback = (e) => {
        localStorage.setItem('locale', e);
        const hash = window.location.hash;
        setLocale(isEnUS(location.hash) ? enUS : zhCN)
        setKey(Math.random() * 1 + 100)
    }

    useEffect(() => {
        addEventListener('hashchange', (ev) => {
            const old = ev.oldURL;
            const newUrl = ev.newURL;
            const oAddr = ev.oldURL.replace(/^.+(?=\/\/)/, '');
            const nAddr = ev.newURL.replace(/^.+(?=\/\/)/, '');

            if (
                oAddr !== nAddr &&
                (nAddr.indexOf('-en') >= 0 || oAddr.indexOf('-en') >= 0) &&
                !((nAddr.indexOf('-en') >= 0) && oAddr.indexOf('-en') >= 0)
            ) {
                setLocale(isEnUS(location.hash) ? enUS : zhCN)
                setKey(Math.random() * 1 + 100)
            }

            const urlArray = [
                
            ]
            const isClear = urlArray.some(key =>{
                return (old.indexOf(key) >= 0 && !(newUrl.indexOf(key) >= 0))
            })

            if(isClear){
                window.sessionStorage.clear();
            }
        })
        return () => {
            removeEventListener('hashchange', () => { })
        };
    }, [location.hash])
    return (
        <IntlProvider key={key} locale={AL.locale} messages={AL.messages}>
            <Provider store={store}>
                <LocaleProvider locale={AL.locale === 'zh-CN' ? zh_CN : en_US}>
                    <div>hello</div>
                </LocaleProvider>
            </Provider>
        </IntlProvider>
    )
}

const RenderApp = () => {
    const [key, setKey] = useState(0);
    function onChange(key) {
        setKey(Math.random() * 1 + 1)
    }
    return (<App onChange={onChange} key={key} />)
}

ReactDOM.render(
    <RenderApp />,
    document.getElementById('root')
);

运行npm start  就可以看见 hello了 其他的路由还需要自己配置,简单方便,需要配置需要配置eslint的还需要单独夹一个文件。

 

【增加router】

/**
//  * Created by zhanglei on 2017/4/29.
//  */
import * as React from 'react';
import { Route, HashRouter, Redirect, Switch } from 'react-router-dom';
import Bundle from './Bundle';
import { connect } from 'react-redux'
import { Button } from 'antd'
import * as PropTypes from 'prop-types'
import { injectIntl } from 'react-intl';
import * as util from '../util'

const { FS, DefMsg, isEnUS } = util;

const LoginIndex = (props) => (
    <Bundle load={() => import('../components/Login/loginIndex')}>
        {(Login) => <Login {...props} />}
    </Bundle>
);


@injectIntl
class MyRouter extends React.Component {
    static childContextTypes = {
        METHOD: PropTypes.object,
        FH: PropTypes.func,
        FS: PropTypes.func,
        DefMsg: PropTypes.func,
        PH: PropTypes.func,
    };
    getChildContext() {
        const { intl } = this.props;
        return {
            DefMsg,
            FS,
            PH: (id) => intl.formatMessage(DefMsg({ id }))
        }
    }
    render() {
        const isShowCN = isEnUS(window.location.hash)
        return (
            <div className={isShowCN ? 'locale-zn-cn' : 'locale-en-us'} style={{ height: '100%' }}><HashRouter>
                    <Switch>
                        <Route exact path="/" component={Login} />
                        <Route path="/app" component={Base} />
                        <Redirect to="/" />
                    </Switch>
                </HashRouter>
            </div>

        )
    }
}
export default MyRouter;

增加redux

import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'
const rootReducer = combineReducers({
    routing: routerReducer,
})

export default rootReducer

调试工具

// 调试工具
import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'

const DevTools = createDevTools(
    <DockMonitor toggleVisibilityKey="ctrl-h" changePositionKey="ctrl-q"  defaultIsVisible={false}>
        <LogMonitor theme="tomorrow" preserveScrollTop={true} />
    </DockMonitor>
)

export default DevTools

按需加载

import React from 'react'
import {Spin} from 'antd';
class Bundle extends React.Component {
  state = {
    mod: null
  }
  constructor(props) {
    super(props);
  }

  async componentDidMount() {
    try {
      const { default: mod } = await this.props.load()
      this.setState({ mod: mod.default || mod })
    } catch (e) {
      console.log(e);
    }

  }

  render() {
    return (this.state.mod ? this.props.children(this.state.mod) : 
    <div style={{textAlign: 'center', padding: '50px 0', backgroundColor: 'white'}}><Spin /></div>)
  }
}

export default Bundle

按需加载js

export function pushScript({ url }) {
    const heads = document.getElementsByTagName('head')[0];
    const isHeadInclude = heads && Array.from(heads.childNodes).some((item) => {
        return item.nodeName === "SCRIPT" && item.src && item.src.includes(url.replace('.', ''));
    })
    const script = document.createElement('script');
    script.src = url;
    return new Promise((resolve, reject) => {
        if (!isHeadInclude) {
            script.onload = function (e) {
                resolve(url)
            }
            script.onerror = function (e) {
                reject(e)
            }
            document.getElementsByTagName('head')[0].appendChild(script);
        } else {
            resolve(url)
        }
    })
}

jsonp  promise

export default function fetchJsonp(_url, options = {}) {
    function generateCallbackFunction() {
        return `jsonp_${Date.now()}_${Math.ceil(Math.random() * 100000)}`;
    }

    function clearFunction(functionName) {
        try {
            delete window[functionName];
        } catch (e) {
            window[functionName] = undefined;
        }
    }

    function removeScript(scriptId) {
        const script = document.getElementById(scriptId);
        if (script) {
            document.getElementsByTagName('head')[0].removeChild(script);
        }
    }
    let url = _url;
    const timeout = options.timeout || 5000;
    const jsonpCallback = options.jsonpCallback || 'callback';

    let timeoutId;

    return new Promise((resolve, reject) => {
        const callbackFunction = options.jsonpCallbackFunction || generateCallbackFunction();
        const scriptId = `${jsonpCallback}_${callbackFunction}`;

        window[callbackFunction] = (response) => {
            resolve({
                ok: true,
                // keep consistent with fetch API
                json: () => Promise.resolve(response),
            });

            if (timeoutId) clearTimeout(timeoutId);

            removeScript(scriptId);

            clearFunction(callbackFunction);
        };

        url += (url.indexOf('?') === -1) ? '?' : '&';

        const jsonpScript = document.createElement('script');
        jsonpScript.setAttribute('src', `${url}${jsonpCallback}=${callbackFunction}`);
        if (options.charset) {
            jsonpScript.setAttribute('charset', options.charset);
        }
        jsonpScript.id = scriptId;
        document.getElementsByTagName('head')[0].appendChild(jsonpScript);

        timeoutId = setTimeout(() => {
            reject(new Error(`JSONP request to ${_url} timed out`));
            clearFunction(callbackFunction);
            removeScript(scriptId);
            window[callbackFunction] = () => {
                clearFunction(callbackFunction);
            };
        }, timeout);

        jsonpScript.onerror = () => {
            reject(new Error(`JSONP request to ${_url} failed`));
            clearFunction(callbackFunction);
            removeScript(scriptId);
            if (timeoutId) clearTimeout(timeoutId);
        };
    });
}

 

export const debounce = (fn, delay) => {
    let timer = null;
    return function (...args) {
        const _this = this;
        clearTimeout(timer);
        timer = setTimeout(function () {
            fn.apply(_this, args)
        }, delay)
    }
}

export const thorttle = (fn, delay) => {
    let timer = null;
    return function (...args) {
        const _this = this;
        if (!timer) {
            timer = setTimeout(function () {
                fn.apply(_this, args);
                timer = null;
            }, delay)
        }
    }
}

其他

var toString = Object.prototype.toString;

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an Array, otherwise false
 */
function isArray(val) {
    return toString.call(val) === '[object Array]';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an ArrayBuffer, otherwise false
 */
function isArrayBuffer(val) {
    return toString.call(val) === '[object ArrayBuffer]';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an FormData, otherwise false
 */
function isFormData(val) {
    return (typeof FormData !== 'undefined') && (val instanceof FormData);
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false
 */
function isArrayBufferView(val) {
    var result;
    if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
        result = ArrayBuffer.isView(val);
    } else {
        result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer);
    }
    return result;
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a String, otherwise false
 */
function isString(val) {
    return typeof val === 'string';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Number, otherwise false
 */
function isNumber(val) {
    return typeof val === 'number';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if the value is undefined, otherwise false
 */
function isUndefined(val) {
    return typeof val === 'undefined';
}

/**
 * Determine if a value is an Object
 *
 * @param {Object} val The value to test
 * @returns {boolean} True if value is an Object, otherwise false
 */
function isObject(val) {
    return val !== null && typeof val === 'object';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Date, otherwise false
 */
function isDate(val) {
    return toString.call(val) === '[object Date]';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a File, otherwise false
 */
function isFile(val) {
    return toString.call(val) === '[object File]';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Blob, otherwise false
 */
function isBlob(val) {
    return toString.call(val) === '[object Blob]';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Function, otherwise false
 */
function isFunction(val) {
    return toString.call(val) === '[object Function]';
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a Stream, otherwise false
 */
function isStream(val) {
    return isObject(val) && isFunction(val.pipe);
}

/**
 * @param {Object} val The value to test
 * @returns {boolean} True if value is a URLSearchParams object, otherwise false
 */
function isURLSearchParams(val) {
    return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
}

/**
 * @param {String} str The String to trim
 * @returns {String} The String freed of excess whitespace
 */
function trim(str) {
    return str.replace(/^\s*/, '').replace(/\s*$/, '');
}

function isStandardBrowserEnv() {
    if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
        navigator.product === 'NativeScript' ||
        navigator.product === 'NS')) {
        return false;
    }
    return (
        typeof window !== 'undefined' &&
        typeof document !== 'undefined'
    );
}

/**
 * @param {Object|Array} obj The object to iterate
 * @param {Function} fn The callback to invoke for each item
 */
function forEach(obj, fn) {
    if (obj === null || typeof obj === 'undefined') {
        return;
    }

    if (typeof obj !== 'object') {
        obj = [obj];
    }

    if (isArray(obj)) {
        for (var i = 0, l = obj.length; i < l; i++) {
            fn.call(null, obj[i], i, obj);
        }
    } else {
        for (var key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                fn.call(null, obj[key], key, obj);
            }
        }
    }
}

/**
 * @param {Object} obj1 Object to merge
 * @returns {Object} Result of all merge properties
 */
function merge(/* obj1, obj2, obj3, ... */) {
    var result = {};
    function assignValue(val, key) {
        if (typeof result[key] === 'object' && typeof val === 'object') {
            result[key] = merge(result[key], val);
        } else {
            result[key] = val;
        }
    }

    for (var i = 0, l = arguments.length; i < l; i++) {
        forEach(arguments[i], assignValue);
    }
    return result;
}

/**
 * @see merge
 * @param {Object} obj1 Object to merge
 * @returns {Object} Result of all merge properties
 */
function deepMerge(/* obj1, obj2, obj3, ... */) {
    var result = {};
    function assignValue(val, key) {
        if (typeof result[key] === 'object' && typeof val === 'object') {
            result[key] = deepMerge(result[key], val);
        } else if (typeof val === 'object') {
            result[key] = deepMerge({}, val);
        } else {
            result[key] = val;
        }
    }

    for (var i = 0, l = arguments.length; i < l; i++) {
        forEach(arguments[i], assignValue);
    }
    return result;
}

 

本地运行

const express = require('express')
const app = express()
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
const openBrowser = require('react-dev-utils/openBrowser');
const proxyMiddleWare = require('http-proxy-middleware');

const os = require('os');
const host = () => {
    const interfaces = os.networkInterfaces();
    const local = '127.0.0.1';
    let ip;
    for (let devName in interfaces) {
        let iface = interfaces[devName];
        for (let i = 0; i < iface.length; i++) {
            let alias = iface[i];
            if (alias.family === 'IPv4' && alias.address !== local && !alias.internal) {
                ip = alias.address;
                break;
            }
        }
    }
    return ip || local;
};

app.engine('.html', ejs.__express);
app.set('view engine', 'html');
app.use(express.static(path.join(__dirname, '../build')));
const router = express.Router();
router.use(function timeLog(req, res, next) {
    next();
});

router.get('/', function (req, res) {
    res.render(path.join(__dirname, '../build/index.html'));
});
app.use('/', router)
const proxyOption = {
    target: 'http://',
    changeOrigoin: true,
    ws: true,
    pathRewrite: { '^/': '/' }
};
app.use("/", proxyMiddleWare(proxyOption));
app.listen(8000, () => {
    openBrowser('http://' + host() + ':8000');
})

 

posted @ 2019-08-13 19:27  <张磊>  阅读(1342)  评论(0编辑  收藏  举报