前提: 需要安装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起的,详细可看以下代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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;
我们可以关注代码中的这两句话,再看看代码上下部分,想必大家也能了解了:
......
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第二章也已发布