React第一篇: 搭建React + nodejs + express框架(nodejs只做前端server)

前提: 需要安装Node.js (>6)版本

 


 

1.cmd进到本地某个目录, 逐行输入以下指令(以下括号为注释)

 

npm install -g create-react-app   (全局安装create-react-app, 默认会安装在C盘个人用户下)

create-react-app my-app (此步安装my-app以及需要的模块到当前文件夹下)

cd my-app (进入到my-app目录)

npm start (启动react项目Demo,可输入localhost:3000进入看demo)

 


 

2.开发模式选择用npm start

 

①首先为什么呢?这涉及到热模块更换(HMR),笼统的说就是更改了JS代码保存后,网页会自动进行刷新,启动新代码。

 

②热模块更换这里也会有bug存在,这和所用的浏览器也会有关,例如有时候刷新页面,会发现代码还是旧的,这点IE11时常会出现该bug。另外关于热模块更换的详细知识会在以后的Webpack编译篇讲解。

 

③官方推荐的npm start指令所对应的server就是以nodejs+express起的,详细可看以下代码:

 

  1 'use strict';
  2 
  3 /* eslint func-names: off */
  4 require('./polyfills');
  5 
  6 const fs = require('fs');
  7 const http = require('http');
  8 const path = require('path');
  9 const url = require('url');
 10 const chokidar = require('chokidar');
 11 const compress = require('compression');
 12 const del = require('del');
 13 const express = require('express');
 14 const httpProxyMiddleware = require('http-proxy-middleware');
 15 const ip = require('ip');
 16 const killable = require('killable');
 17 const serveIndex = require('serve-index');
 18 const historyApiFallback = require('connect-history-api-fallback');
 19 const selfsigned = require('selfsigned');
 20 const sockjs = require('sockjs');
 21 const spdy = require('spdy');
 22 const webpack = require('webpack');
 23 const webpackDevMiddleware = require('webpack-dev-middleware');
 24 const OptionsValidationError = require('./OptionsValidationError');
 25 const optionsSchema = require('./optionsSchema.json');
 26 
 27 const clientStats = { errorDetails: false };
 28 const log = console.log; // eslint-disable-line no-console
 29 
 30 function Server(compiler, options) {
 31   // Default options
 32   if (!options) options = {};
 33 
 34   const validationErrors = webpack.validateSchema(optionsSchema, options);
 35   if (validationErrors.length) {
 36     throw new OptionsValidationError(validationErrors);
 37   }
 38 
 39   if (options.lazy && !options.filename) {
 40     throw new Error("'filename' option must be set in lazy mode.");
 41   }
 42 
 43   this.hot = options.hot || options.hotOnly;
 44   this.headers = options.headers;
 45   this.clientLogLevel = options.clientLogLevel;
 46   this.clientOverlay = options.overlay;
 47   this.progress = options.progress;
 48   this.disableHostCheck = !!options.disableHostCheck;
 49   this.publicHost = options.public;
 50   this.allowedHosts = options.allowedHosts;
 51   this.sockets = [];
 52   this.contentBaseWatchers = [];
 53 
 54   // Listening for events
 55   const invalidPlugin = () => {
 56     this.sockWrite(this.sockets, 'invalid');
 57   };
 58   if (this.progress) {
 59     const progressPlugin = new webpack.ProgressPlugin((percent, msg, addInfo) => {
 60       percent = Math.floor(percent * 100);
 61       if (percent === 100) msg = 'Compilation completed';
 62       if (addInfo) msg = `${msg} (${addInfo})`;
 63       this.sockWrite(this.sockets, 'progress-update', { percent, msg });
 64     });
 65     compiler.apply(progressPlugin);
 66   }
 67   compiler.plugin('compile', invalidPlugin);
 68   compiler.plugin('invalid', invalidPlugin);
 69   compiler.plugin('done', (stats) => {
 70     this._sendStats(this.sockets, stats.toJson(clientStats));
 71     this._stats = stats;
 72   });
 73 
 74   // Init express server
 75   const app = this.app = new express(); // eslint-disable-line
 76 
 77   app.all('*', (req, res, next) => { // eslint-disable-line
 78     if (this.checkHost(req.headers)) { return next(); }
 79     res.send('Invalid Host header');
 80   });
 81 
 82   // middleware for serving webpack bundle
 83   this.middleware = webpackDevMiddleware(compiler, options);
 84 
 85   app.get('/__webpack_dev_server__/live.bundle.js', (req, res) => {
 86     res.setHeader('Content-Type', 'application/javascript');
 87     fs.createReadStream(path.join(__dirname, '..', 'client', 'live.bundle.js')).pipe(res);
 88   });
 89 
 90   app.get('/__webpack_dev_server__/sockjs.bundle.js', (req, res) => {
 91     res.setHeader('Content-Type', 'application/javascript');
 92     fs.createReadStream(path.join(__dirname, '..', 'client', 'sockjs.bundle.js')).pipe(res);
 93   });
 94 
 95   app.get('/webpack-dev-server.js', (req, res) => {
 96     res.setHeader('Content-Type', 'application/javascript');
 97     fs.createReadStream(path.join(__dirname, '..', 'client', 'index.bundle.js')).pipe(res);
 98   });
 99 
100   app.get('/webpack-dev-server/*', (req, res) => {
101     res.setHeader('Content-Type', 'text/html');
102     fs.createReadStream(path.join(__dirname, '..', 'client', 'live.html')).pipe(res);
103   });
104 
105   app.get('/webpack-dev-server', (req, res) => {
106     res.setHeader('Content-Type', 'text/html');
107     res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>');
108     const outputPath = this.middleware.getFilenameFromUrl(options.publicPath || '/');
109     const filesystem = this.middleware.fileSystem;
110 
111     function writeDirectory(baseUrl, basePath) {
112       const content = filesystem.readdirSync(basePath);
113       res.write('<ul>');
114       content.forEach((item) => {
115         const p = `${basePath}/${item}`;
116         if (filesystem.statSync(p).isFile()) {
117           res.write('<li><a href="');
118           res.write(baseUrl + item);
119           res.write('">');
120           res.write(item);
121           res.write('</a></li>');
122           if (/\.js$/.test(item)) {
123             const htmlItem = item.substr(0, item.length - 3);
124             res.write('<li><a href="');
125             res.write(baseUrl + htmlItem);
126             res.write('">');
127             res.write(htmlItem);
128             res.write('</a> (magic html for ');
129             res.write(item);
130             res.write(') (<a href="');
131             res.write(baseUrl.replace(/(^(https?:\/\/[^\/]+)?\/)/, "$1webpack-dev-server/") + htmlItem); // eslint-disable-line
132             res.write('">webpack-dev-server</a>)</li>');
133           }
134         } else {
135           res.write('<li>');
136           res.write(item);
137           res.write('<br>');
138           writeDirectory(`${baseUrl + item}/`, p);
139           res.write('</li>');
140         }
141       });
142       res.write('</ul>');
143     }
144     writeDirectory(options.publicPath || '/', outputPath);
145     res.end('</body></html>');
146   });
147 
148   let contentBase;
149   if (options.contentBase !== undefined) { // eslint-disable-line
150     contentBase = options.contentBase; // eslint-disable-line
151   } else {
152     contentBase = process.cwd();
153   }
154 
155   // Keep track of websocket proxies for external websocket upgrade.
156   const websocketProxies = [];
157 
158   const features = {
159     compress() {
160       if (options.compress) {
161         // Enable gzip compression.
162         app.use(compress());
163       }
164     },
165 
166     proxy() {
167       if (options.proxy) {
168         /**
169           * Assume a proxy configuration specified as:
170           * proxy: {
171           *   'context': { options }
172           * }
173           * OR
174           * proxy: {
175           *   'context': 'target'
176           * }
177           */
178         if (!Array.isArray(options.proxy)) {
179           options.proxy = Object.keys(options.proxy).map((context) => {
180             let proxyOptions;
181             // For backwards compatibility reasons.
182             const correctedContext = context.replace(/^\*$/, '**').replace(/\/\*$/, '');
183 
184             if (typeof options.proxy[context] === 'string') {
185               proxyOptions = {
186                 context: correctedContext,
187                 target: options.proxy[context]
188               };
189             } else {
190               proxyOptions = Object.assign({}, options.proxy[context]);
191               proxyOptions.context = correctedContext;
192             }
193             proxyOptions.logLevel = proxyOptions.logLevel || 'warn';
194 
195             return proxyOptions;
196           });
197         }
198 
199         const getProxyMiddleware = (proxyConfig) => {
200           const context = proxyConfig.context || proxyConfig.path;
201 
202           // It is possible to use the `bypass` method without a `target`.
203           // However, the proxy middleware has no use in this case, and will fail to instantiate.
204           if (proxyConfig.target) {
205             return httpProxyMiddleware(context, proxyConfig);
206           }
207         };
208 
209         /**
210         * Assume a proxy configuration specified as:
211         * proxy: [
212         *   {
213         *     context: ...,
214         *     ...options...
215         *   },
216         *   // or:
217         *   function() {
218         *     return {
219         *       context: ...,
220         *       ...options...
221         *     };
222         *   }
223         * ]
224         */
225         options.proxy.forEach((proxyConfigOrCallback) => {
226           let proxyConfig;
227           let proxyMiddleware;
228 
229           if (typeof proxyConfigOrCallback === 'function') {
230             proxyConfig = proxyConfigOrCallback();
231           } else {
232             proxyConfig = proxyConfigOrCallback;
233           }
234 
235           proxyMiddleware = getProxyMiddleware(proxyConfig);
236           if (proxyConfig.ws) {
237             websocketProxies.push(proxyMiddleware);
238           }
239 
240           app.use((req, res, next) => {
241             if (typeof proxyConfigOrCallback === 'function') {
242               const newProxyConfig = proxyConfigOrCallback();
243               if (newProxyConfig !== proxyConfig) {
244                 proxyConfig = newProxyConfig;
245                 proxyMiddleware = getProxyMiddleware(proxyConfig);
246               }
247             }
248             const bypass = typeof proxyConfig.bypass === 'function';
249             // eslint-disable-next-line
250             const bypassUrl = bypass && proxyConfig.bypass(req, res, proxyConfig) || false;
251 
252             if (bypassUrl) {
253               req.url = bypassUrl;
254               next();
255             } else if (proxyMiddleware) {
256               return proxyMiddleware(req, res, next);
257             } else {
258               next();
259             }
260           });
261         });
262       }
263     },
264 
265     historyApiFallback() {
266       if (options.historyApiFallback) {
267         // Fall back to /index.html if nothing else matches.
268         app.use(historyApiFallback(typeof options.historyApiFallback === 'object' ? options.historyApiFallback : null));
269       }
270     },
271 
272     contentBaseFiles() {
273       if (Array.isArray(contentBase)) {
274         contentBase.forEach((item) => {
275           app.get('*', express.static(item));
276         });
277       } else if (/^(https?:)?\/\//.test(contentBase)) {
278         log('Using a URL as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.');
279         log('proxy: {\n\t"*": "<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
280         // Redirect every request to contentBase
281         app.get('*', (req, res) => {
282           res.writeHead(302, {
283             Location: contentBase + req.path + (req._parsedUrl.search || '')
284           });
285           res.end();
286         });
287       } else if (typeof contentBase === 'number') {
288         log('Using a number as contentBase is deprecated and will be removed in the next major version. Please use the proxy option instead.');
289         log('proxy: {\n\t"*": "//localhost:<your current contentBase configuration>"\n}'); // eslint-disable-line quotes
290         // Redirect every request to the port contentBase
291         app.get('*', (req, res) => {
292           res.writeHead(302, {
293             Location: `//localhost:${contentBase}${req.path}${req._parsedUrl.search || ''}`
294           });
295           res.end();
296         });
297       } else {
298         // route content request
299         app.get('*', express.static(contentBase, options.staticOptions));
300       }
301     },
302 
303     contentBaseIndex() {
304       if (Array.isArray(contentBase)) {
305         contentBase.forEach((item) => {
306           app.get('*', serveIndex(item));
307         });
308       } else if (!/^(https?:)?\/\//.test(contentBase) && typeof contentBase !== 'number') {
309         app.get('*', serveIndex(contentBase));
310       }
311     },
312 
313     watchContentBase: () => {
314       if (/^(https?:)?\/\//.test(contentBase) || typeof contentBase === 'number') {
315         throw new Error('Watching remote files is not supported.');
316       } else if (Array.isArray(contentBase)) {
317         contentBase.forEach((item) => {
318           this._watch(item);
319         });
320       } else {
321         this._watch(contentBase);
322       }
323     },
324 
325     before: () => {
326       if (typeof options.before === 'function') { options.before(app, this); }
327     },
328 
329     middleware: () => {
330       // include our middleware to ensure it is able to handle '/index.html' request after redirect
331       app.use(this.middleware);
332     },
333 
334     after: () => {
335       if (typeof options.after === 'function') { options.after(app, this); }
336     },
337 
338     headers: () => {
339       app.all('*', this.setContentHeaders.bind(this));
340     },
341 
342     magicHtml: () => {
343       app.get('*', this.serveMagicHtml.bind(this));
344     },
345 
346     setup: () => {
347       if (typeof options.setup === 'function') {
348         log('The `setup` option is deprecated and will be removed in v3. Please update your config to use `before`');
349         options.setup(app, this);
350       }
351     }
352   };
353 
354   const defaultFeatures = ['before', 'setup', 'headers', 'middleware'];
355   if (options.proxy) { defaultFeatures.push('proxy', 'middleware'); }
356   if (contentBase !== false) { defaultFeatures.push('contentBaseFiles'); }
357   if (options.watchContentBase) { defaultFeatures.push('watchContentBase'); }
358   if (options.historyApiFallback) {
359     defaultFeatures.push('historyApiFallback', 'middleware');
360     if (contentBase !== false) { defaultFeatures.push('contentBaseFiles'); }
361   }
362   defaultFeatures.push('magicHtml');
363   if (contentBase !== false) { defaultFeatures.push('contentBaseIndex'); }
364   // compress is placed last and uses unshift so that it will be the first middleware used
365   if (options.compress) { defaultFeatures.unshift('compress'); }
366   if (options.after) { defaultFeatures.push('after'); }
367 
368   (options.features || defaultFeatures).forEach((feature) => {
369     features[feature]();
370   });
371 
372   if (options.https) {
373     // for keep supporting CLI parameters
374     if (typeof options.https === 'boolean') {
375       options.https = {
376         key: options.key,
377         cert: options.cert,
378         ca: options.ca,
379         pfx: options.pfx,
380         passphrase: options.pfxPassphrase,
381         requestCert: options.requestCert || false
382       };
383     }
384 
385     let fakeCert;
386     if (!options.https.key || !options.https.cert) {
387       // Use a self-signed certificate if no certificate was configured.
388       // Cycle certs every 24 hours
389       const certPath = path.join(__dirname, '../ssl/server.pem');
390       let certExists = fs.existsSync(certPath);
391 
392       if (certExists) {
393         const certStat = fs.statSync(certPath);
394         const certTtl = 1000 * 60 * 60 * 24;
395         const now = new Date();
396 
397         // cert is more than 30 days old, kill it with fire
398         if ((now - certStat.ctime) / certTtl > 30) {
399           log('SSL Certificate is more than 30 days old. Removing.');
400           del.sync([certPath], { force: true });
401           certExists = false;
402         }
403       }
404 
405       if (!certExists) {
406         log('Generating SSL Certificate');
407         const attrs = [{ name: 'commonName', value: 'localhost' }];
408         const pems = selfsigned.generate(attrs, {
409           algorithm: 'sha256',
410           days: 30,
411           keySize: 2048,
412           extensions: [{
413             name: 'basicConstraints',
414             cA: true
415           }, {
416             name: 'keyUsage',
417             keyCertSign: true,
418             digitalSignature: true,
419             nonRepudiation: true,
420             keyEncipherment: true,
421             dataEncipherment: true
422           }, {
423             name: 'subjectAltName',
424             altNames: [
425               {
426                 // type 2 is DNS
427                 type: 2,
428                 value: 'localhost'
429               },
430               {
431                 type: 2,
432                 value: 'localhost.localdomain'
433               },
434               {
435                 type: 2,
436                 value: 'lvh.me'
437               },
438               {
439                 type: 2,
440                 value: '*.lvh.me'
441               },
442               {
443                 type: 2,
444                 value: '[::1]'
445               },
446               {
447                 // type 7 is IP
448                 type: 7,
449                 ip: '127.0.0.1'
450               },
451               {
452                 type: 7,
453                 ip: 'fe80::1'
454               }
455             ]
456           }]
457         });
458 
459         fs.writeFileSync(certPath, pems.private + pems.cert, { encoding: 'utf-8' });
460       }
461       fakeCert = fs.readFileSync(certPath);
462     }
463 
464     options.https.key = options.https.key || fakeCert;
465     options.https.cert = options.https.cert || fakeCert;
466 
467     if (!options.https.spdy) {
468       options.https.spdy = {
469         protocols: ['h2', 'http/1.1']
470       };
471     }
472 
473     this.listeningApp = spdy.createServer(options.https, app);
474   } else {
475     this.listeningApp = http.createServer(app);
476   }
477 
478   killable(this.listeningApp);
479 
480   // Proxy websockets without the initial http request
481   // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
482   websocketProxies.forEach(function (wsProxy) {
483     this.listeningApp.on('upgrade', wsProxy.upgrade);
484   }, this);
485 }
486 
487 Server.prototype.use = function () {
488   // eslint-disable-next-line
489   this.app.use.apply(this.app, arguments);
490 };
491 
492 Server.prototype.setContentHeaders = function (req, res, next) {
493   if (this.headers) {
494     for (const name in this.headers) { // eslint-disable-line
495       res.setHeader(name, this.headers[name]);
496     }
497   }
498 
499   next();
500 };
501 
502 Server.prototype.checkHost = function (headers) {
503   // allow user to opt-out this security check, at own risk
504   if (this.disableHostCheck) return true;
505 
506   // get the Host header and extract hostname
507   // we don't care about port not matching
508   const hostHeader = headers.host;
509   if (!hostHeader) return false;
510 
511   // use the node url-parser to retrieve the hostname from the host-header.
512   const hostname = url.parse(`//${hostHeader}`, false, true).hostname;
513 
514   // always allow requests with explicit IPv4 or IPv6-address.
515   // A note on IPv6 addresses: hostHeader will always contain the brackets denoting
516   // an IPv6-address in URLs, these are removed from the hostname in url.parse(),
517   // so we have the pure IPv6-address in hostname.
518   if (ip.isV4Format(hostname) || ip.isV6Format(hostname)) return true;
519 
520   // always allow localhost host, for convience
521   if (hostname === 'localhost') return true;
522 
523   // allow if hostname is in allowedHosts
524   if (this.allowedHosts && this.allowedHosts.length) {
525     for (let hostIdx = 0; hostIdx < this.allowedHosts.length; hostIdx++) {
526       const allowedHost = this.allowedHosts[hostIdx];
527       if (allowedHost === hostname) return true;
528 
529       // support "." as a subdomain wildcard
530       // e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
531       if (allowedHost[0] === '.') {
532         // "example.com"
533         if (hostname === allowedHost.substring(1)) return true;
534         // "*.example.com"
535         if (hostname.endsWith(allowedHost)) return true;
536       }
537     }
538   }
539 
540   // allow hostname of listening adress
541   if (hostname === this.listenHostname) return true;
542 
543   // also allow public hostname if provided
544   if (typeof this.publicHost === 'string') {
545     const idxPublic = this.publicHost.indexOf(':');
546     const publicHostname = idxPublic >= 0 ? this.publicHost.substr(0, idxPublic) : this.publicHost;
547     if (hostname === publicHostname) return true;
548   }
549 
550   // disallow
551   return false;
552 };
553 
554 // delegate listen call and init sockjs
555 Server.prototype.listen = function (port, hostname, fn) {
556   this.listenHostname = hostname;
557   // eslint-disable-next-line
558 
559   const returnValue = this.listeningApp.listen(port, hostname, (err) => {
560     const sockServer = sockjs.createServer({
561       // Use provided up-to-date sockjs-client
562       sockjs_url: '/__webpack_dev_server__/sockjs.bundle.js',
563       // Limit useless logs
564       log(severity, line) {
565         if (severity === 'error') {
566           log(line);
567         }
568       }
569     });
570 
571     sockServer.on('connection', (conn) => {
572       if (!conn) return;
573       if (!this.checkHost(conn.headers)) {
574         this.sockWrite([conn], 'error', 'Invalid Host header');
575         conn.close();
576         return;
577       }
578       this.sockets.push(conn);
579 
580       conn.on('close', () => {
581         const connIndex = this.sockets.indexOf(conn);
582         if (connIndex >= 0) {
583           this.sockets.splice(connIndex, 1);
584         }
585       });
586 
587       if (this.clientLogLevel) { this.sockWrite([conn], 'log-level', this.clientLogLevel); }
588 
589       if (this.progress) { this.sockWrite([conn], 'progress', this.progress); }
590 
591       if (this.clientOverlay) { this.sockWrite([conn], 'overlay', this.clientOverlay); }
592 
593       if (this.hot) this.sockWrite([conn], 'hot');
594 
595       if (!this._stats) return;
596       this._sendStats([conn], this._stats.toJson(clientStats), true);
597     });
598 
599     sockServer.installHandlers(this.listeningApp, {
600       prefix: '/sockjs-node'
601     });
602 
603     if (fn) {
604       fn.call(this.listeningApp, err);
605     }
606   });
607 
608   return returnValue;
609 };
610 
611 Server.prototype.close = function (callback) {
612   this.sockets.forEach((sock) => {
613     sock.close();
614   });
615   this.sockets = [];
616 
617   this.contentBaseWatchers.forEach((watcher) => {
618     watcher.close();
619   });
620   this.contentBaseWatchers = [];
621 
622   this.listeningApp.kill(() => {
623     this.middleware.close(callback);
624   });
625 };
626 
627 Server.prototype.sockWrite = function (sockets, type, data) {
628   sockets.forEach((sock) => {
629     sock.write(JSON.stringify({
630       type,
631       data
632     }));
633   });
634 };
635 
636 Server.prototype.serveMagicHtml = function (req, res, next) {
637   const _path = req.path;
638   try {
639     if (!this.middleware.fileSystem.statSync(this.middleware.getFilenameFromUrl(`${_path}.js`)).isFile()) { return next(); }
640     // Serve a page that executes the javascript
641     res.write('<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body><script type="text/javascript" charset="utf-8" src="');
642     res.write(_path);
643     res.write('.js');
644     res.write(req._parsedUrl.search || '');
645     res.end('"></script></body></html>');
646   } catch (e) {
647     return next();
648   }
649 };
650 
651 // send stats to a socket or multiple sockets
652 Server.prototype._sendStats = function (sockets, stats, force) {
653   if (!force &&
654   stats &&
655   (!stats.errors || stats.errors.length === 0) &&
656   stats.assets &&
657   stats.assets.every(asset => !asset.emitted)
658   ) { return this.sockWrite(sockets, 'still-ok'); }
659   this.sockWrite(sockets, 'hash', stats.hash);
660   if (stats.errors.length > 0) { this.sockWrite(sockets, 'errors', stats.errors); } else if (stats.warnings.length > 0) { this.sockWrite(sockets, 'warnings', stats.warnings); } else { this.sockWrite(sockets, 'ok'); }
661 };
662 
663 Server.prototype._watch = function (watchPath) {
664   const watcher = chokidar.watch(watchPath).on('change', () => {
665     this.sockWrite(this.sockets, 'content-changed');
666   });
667 
668   this.contentBaseWatchers.push(watcher);
669 };
670 
671 Server.prototype.invalidate = function () {
672   if (this.middleware) this.middleware.invalidate();
673 };
674 
675 // Export this logic, so that other implementations, like task-runners can use it
676 Server.addDevServerEntrypoints = require('./util/addDevServerEntrypoints');
677 
678 module.exports = Server;
View Code

 

 

我们可以关注代码中的这两句话,再看看代码上下部分,想必大家也能了解了:

 

......

const express = require('express');   

......

const app = this.app = new express(); // eslint-disable-line

......

 

④:那么要当做产品发布的话也用npm start么? NO NO NO, 详细可看我们的第三大步

 


 

3.架设服务的话就需要编译发布代码,此时需要做以下行为:

 

①cmd下输入以下指令:npm run build (run build的定义在package.json里,调用的是webpack.config.prod.js)

 

 ②编译完成后,本地会出现build文件夹,如图

③这里不用推荐的npm install -g serve的模块做server,这样之后弄不了反向代理。这里推荐用node+express来写一个简单的server,新建一个文件,命名为server,js,代码如下,将其保存于build目录下(本地注了释反向代理设置,如果需要去掉注释即可,端口可自行设置):

const express = require('express')
const proxy = require('http-proxy-middleware')

/*const options = {
    target: "http://localhost:8080",
    changeOrigin:true,
}

const apiProxy = proxy(options)*/

const app = express()

app.use(express.static(__dirname))
//app.use('/', apiProxy)
app.listen(80)

 

 

 ④cd进build目录,执行指令node server.js, 打开浏览器输入localhost:80就可看到成果了

 

关于后台搭建请见“后台springboot分类”里的springboot框架搭建,React第二章也已发布

 

posted @ 2018-05-01 17:05  天叔  阅读(18466)  评论(0编辑  收藏  举报