首先是配置文件package.json,裏邊包括所用到的babel以及關於react的一些依賴包。javascript
{ "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.jscss
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.jshtml
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方法html5
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) }); }
開發環境基本上搭建完成。開始搭建生產環境java
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代理一個生產環境node
最後須要在public目錄下增長html頁面react
<!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入口文件。jquery
會用到react-redux 作了初步的國際化方案webpack
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的還須要單獨夾一個文件。ios
【增長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'); })