搭建react框架(会持续更新,加入各个集成模块)
Posted 天叔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搭建react框架(会持续更新,加入各个集成模块)相关的知识,希望对你有一定的参考价值。
前提: 需要安装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, 以上是关于搭建react框架(会持续更新,加入各个集成模块)的主要内容,如果未能解决你的问题,请参考以下文章(头条新闻)Cordova+React+OnsenUI+Redux新闻App开发实战教程