从零搭建 React 开发环境
1 前言 2 大概在 2019 年,自己搭建 React 开发环境的想法萌芽,到目前为止,公司的很多项目上,也在使用中,比较稳定。为什么要自己造轮子?起初是因为自己并不满意市面上的脚手架。另外,造轮子对于自己也有一些技术上的帮助,学别人二次封装的东西,不如直接使用底层的库,这样也有助于自己系统的学习一遍知识,废话不多说,直接进入正文,如何搭建自己的开发环境。 3 4 初始化 5 创建文件夹并进入: 6 7 $ mkdir tristana && cd tristana 8 初始化 package.json 9 10 $ npm init 11 安装 Webpack 12 13 $ npm install webpack webpack-cli --save-dev 14 创建以下目录结构、文件和内容: 15 16 project 17 tristana 18 |- package.json 19 |- /dist 20 |- index.html 21 |- /script 22 |- webpack.config.js 23 |- index.html 24 |- /src 25 |- index.js 26 src/index.js 27 document.getElementById("root").append("React"); 28 index.html && dist/index.html 29 <!DOCTYPE html> 30 <html> 31 <head> 32 <meta charset="utf-8" /> 33 <title>tristana</title> 34 </head> 35 <body> 36 <script src="../src/index.js"></script> 37 <div id="root"></div> 38 </body> 39 </html> 40 script/webpack.config.js 41 module.exports = { 42 mode: "development", 43 entry: "./src/index.js", 44 }; 45 package.json 46 { 47 // ... 48 "scripts": { 49 "build": "webpack --mode=development --config script/webpack.config.js" 50 }, 51 } 52 然后根目录终端输入:npm run build 53 54 在浏览器中打开 dist 目录下的 index.html,如果一切正常,你应该能看到以下文本:'React' 55 56 index.html 目前放在 dist 目录下,但它是手动创建的,下面会教你如何生成 index.html 而非手动编辑它。 57 58 Webpack 核心功能 59 Babel 60 $ npm install @babel/cli @babel/core babel-loader @babel/preset-env --save-dev 61 script/webpack.config.js 62 module.exports = { 63 // ... 64 module: { 65 rules: [ 66 { 67 test: /\.(js|jsx)$/, 68 loader: "babel-loader", 69 exclude: /node_modules/, 70 }, 71 ], 72 }, 73 }; 74 .babelrc 75 在根目录下添加 .babelrc 文件: 76 77 { 78 "presets": ["@babel/preset-env", "@babel/preset-react"] 79 } 80 样式 81 $ npm install style-loader css-loader less less-loader --save-dev 82 script/webpack.config.js 83 module.exports = { 84 // ... 85 module: { 86 rules: [ 87 { 88 test: /\.(css|less)$/, 89 use: [ 90 { 91 loader: "style-loader", 92 }, 93 { 94 loader: "css-loader", 95 options: { 96 importLoaders: 1, 97 }, 98 }, 99 { 100 loader: "less-loader", 101 lessOptions: { 102 javascriptEnabled: true, 103 }, 104 }, 105 ], 106 }, 107 ], 108 }, 109 }; 110 图片字体 111 $ npm install file-loader --save-dev 112 script/webpack.config.js 113 module.exports = { 114 // ... 115 module: { 116 rules: [ 117 { 118 test: /\.(png|svg|jpg|gif|jpeg)$/, 119 loader: 'file-loader' 120 }, 121 { 122 test: /\.(woff|woff2|eot|ttf|otf)$/, 123 loader: 'file-loader' 124 } 125 ], 126 }, 127 }; 128 HTML 129 $ npm install html-webpack-plugin --save-dev 130 script/webpack.config.js 131 const HtmlWebpackPlugin = require('html-webpack-plugin'); 132 module.exports = { 133 // ... 134 plugins: { 135 html: new HtmlWebpackPlugin({ 136 title: 'tristana', 137 template: 'public/index.html' 138 }), 139 } 140 }; 141 index.html 142 <!DOCTYPE html> 143 <html> 144 <head> 145 <meta charset="utf-8" /> 146 <title>tristana</title> 147 </head> 148 <body> 149 <div id="root"></div> 150 </body> 151 </html> 152 开发服务 153 $ npm install webpack-dev-server --save-dev 154 script/webpack.config.js 155 const path = require("path"); 156 const HtmlWebpackPlugin = require('html-webpack-plugin'); 157 module.exports = { 158 // ... 159 devServer: { 160 contentBase: path.resolve(__dirname, "dist"), 161 hot: true, 162 historyApiFallback: true, 163 compress: true, 164 }, 165 }; 166 package.json 167 { 168 // ... 169 "scripts": { 170 "start": "webpack serve --mode=development --config script/webpack.config.js" 171 }, 172 // ... 173 } 174 清理 dist 175 $ npm install clean-webpack-plugin --save-dev 176 script/webpack.config.js 177 const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 178 module.exports = { 179 // ... 180 plugins: { 181 new CleanWebpackPlugin() 182 } 183 }; 184 Tips 185 由于 webpack 使用的是^5.21.2 版本,在使用该插件时,会提示clean-webpack-plugin: options.output.path not defined. Plugin disabled...,暂时还未解决。 186 187 环境变量 188 $ npm install cross-env --save-dev 189 package.json 190 { 191 // ... 192 "scripts": { 193 "start": "cross-env ENV_LWD=development webpack serve --mode=development --config script/webpack.config.js", 194 "build": "cross-env ENV_LWD=production webpack --mode=production --config script/webpack.config.js" 195 }, 196 // ... 197 } 198 .jsx 文件 199 安装依赖 200 $ npm install @babel/preset-react react react-dom --save-dev 201 .babelrc 202 { 203 "presets": ["@babel/preset-env", "@babel/preset-react"] 204 } 205 src/App.jsx 206 在 src 目录下,新增 App.jsx 文件: 207 208 import React, { Component } from "react"; 209 210 class App extends Component { 211 render() { 212 return ( 213 <div> 214 <h1> Hello, World! </h1> 215 </div> 216 ); 217 } 218 } 219 220 export default App; 221 src/index.js 222 import React from "react"; 223 import ReactDOM from "react-dom"; 224 import App from "./App.jsx"; 225 ReactDOM.render(<App />, document.getElementById("root")); 226 React Router 227 安装依赖 228 $ npm install react-router history --save 229 src/index.js 230 import React from "react"; 231 import ReactDOM from "react-dom"; 232 import { Router, Route, Link } from "react-router"; 233 import { createBrowserHistory } from "history"; 234 import App from "./App.jsx"; 235 236 const About = () => { 237 return <>About</>; 238 }; 239 240 ReactDOM.render( 241 <Router history={createBrowserHistory()}> 242 <Route path="/" component={App} /> 243 <Route path="/about" component={About} /> 244 </Router>, 245 document.getElementById("root") 246 ); 247 MobX 248 安装依赖 249 $ npm install mobx mobx-react babel-preset-mobx --save 250 .babelrc 251 { 252 "presets": ["@babel/preset-env", "@babel/preset-react", "mobx"] 253 } 254 src/store.js 255 在 src 目录下新建 store.js 256 257 import { observable, action, makeObservable } from "mobx"; 258 259 class Store { 260 261 constructor() { 262 makeObservable(this); 263 } 264 265 @observable 266 count = 0; 267 268 @action("add") 269 add = () => { 270 this.count = this.count + 1; 271 }; 272 273 @action("reduce") 274 reduce = () => { 275 this.count = this.count - 1; 276 }; 277 } 278 export default new Store(); 279 index.js 280 import { Provider } from "mobx-react"; 281 import Store from "./store"; 282 // ... 283 ReactDOM.render( 284 <Provider store={Store}> 285 <Router history={createBrowserHistory()}> 286 <Route path="/" component={App} /> 287 <Route path="/about" component={About} /> 288 </Router> 289 </Provider>, 290 document.getElementById("root") 291 ); 292 src/App.jsx 293 import React, { Component } from "react"; 294 import { observer, inject } from "mobx-react"; 295 296 @inject("store") 297 @observer 298 class App extends Component { 299 render() { 300 return ( 301 <div> 302 <div>{this.props.store.count}</div> 303 <button onClick={this.props.store.add}>add</button> 304 <button onClick={this.props.store.reduce}>reduce</button> 305 </div> 306 ); 307 } 308 } 309 310 export default App; 311 Ant Design 312 安装依赖 313 $ npm install antd babel-plugin-import --save 314 .babelrc 315 { 316 // ... 317 "plugins": [ 318 [ 319 "import", 320 { 321 "libraryName": "antd", 322 "libraryDirectory": "es", 323 "style": true 324 } 325 ] 326 ] 327 } 328 src/App.jsx 329 // ... 330 import { DatePicker } from "antd"; 331 import "antd/dist/antd.css"; 332 333 @inject("store") 334 @observer 335 class App extends Component { 336 render() { 337 return ( 338 <div> 339 <DatePicker /> 340 </div> 341 ); 342 } 343 } 344 345 export default App; 346 TypeScript 347 安装依赖 348 $ npm install typescript @babel/preset-typescript --save-dev 349 .babelrc 350 { 351 "presets": [ 352 // ... 353 "@babel/preset-typescript" 354 ] 355 } 356 tsconfig.json 357 在根目录下,新增 tsconfig.json 文件: 358 359 { 360 "compilerOptions": { 361 "emitDecoratorMetadata": true, 362 "experimentalDecorators": true, 363 "target": "ES5", 364 "allowSyntheticDefaultImports": true, 365 "strict": true, 366 "forceConsistentCasingInFileNames": true, 367 "allowJs": true, 368 "outDir": "./dist/", 369 "esModuleInterop": true, 370 "noImplicitAny": false, 371 "sourceMap": true, 372 "module": "esnext", 373 "moduleResolution": "node", 374 "isolatedModules": true, 375 "importHelpers": true, 376 "lib": ["esnext", "dom", "dom.iterable"], 377 "skipLibCheck": true, 378 "jsx": "react", 379 "typeRoots": ["node", "node_modules/@types"], 380 "rootDirs": ["./src"], 381 "baseUrl": "./src" 382 }, 383 "include": ["./src/**/*"], 384 "exclude": ["node_modules"] 385 } 386 src/App.jsx 387 更换文件后缀 App.jsx -> App.tsx 388 389 import React, { Component } from "react"; 390 import { observer, inject } from "mobx-react"; 391 import { DatePicker } from "antd"; 392 import "antd/dist/antd.css"; 393 394 @inject("store") 395 @observer 396 class App extends Component { 397 props: any; 398 render() { 399 return ( 400 <div> 401 <DatePicker /> 402 <div>{this.props.store.count}</div> 403 <button onClick={this.props.store.add}>add</button> 404 <button onClick={this.props.store.reduce}>reduce</button> 405 </div> 406 ); 407 } 408 } 409 410 export default App; 411 代码规范 412 代码校验、代码格式化、Git 提交前校验、Vscode配置、编译校验 413 414 ESLint 415 安装依赖 416 $ npm install @typescript-eslint/parser eslint eslint-plugin-standard @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-promise --save-dev 417 .eslintrc.js 418 在根目录下,新增 .eslintrc.js 文件: 419 420 module.exports = { 421 extends: ["eslint:recommended", "plugin:react/recommended"], 422 env: { 423 browser: true, 424 commonjs: true, 425 es6: true, 426 }, 427 globals: { 428 $: true, 429 process: true, 430 __dirname: true, 431 }, 432 parser: "@typescript-eslint/parser", 433 parserOptions: { 434 ecmaFeatures: { 435 jsx: true, 436 modules: true, 437 }, 438 sourceType: "module", 439 ecmaVersion: 6, 440 }, 441 plugins: ["react", "standard", "promise", "@typescript-eslint"], 442 settings: { 443 "import/ignore": ["node_modules"], 444 react: { 445 version: "latest", 446 }, 447 }, 448 rules: { 449 quotes: [2, "single"], 450 "no-console": 0, 451 "no-debugger": 1, 452 "no-var": 1, 453 semi: ["error", "always"], 454 "no-irregular-whitespace": 0, 455 "no-trailing-spaces": 1, 456 "eol-last": 0, 457 "no-unused-vars": [ 458 1, 459 { 460 vars: "all", 461 args: "after-used", 462 }, 463 ], 464 "no-case-declarations": 0, 465 "no-underscore-dangle": 0, 466 "no-alert": 2, 467 "no-lone-blocks": 0, 468 "no-class-assign": 2, 469 "no-cond-assign": 2, 470 "no-const-assign": 2, 471 "no-delete-var": 2, 472 "no-dupe-keys": 2, 473 "use-isnan": 2, 474 "no-duplicate-case": 2, 475 "no-dupe-args": 2, 476 "no-empty": 2, 477 "no-func-assign": 2, 478 "no-invalid-this": 0, 479 "no-redeclare": 2, 480 "no-spaced-func": 2, 481 "no-this-before-super": 0, 482 "no-undef": 2, 483 "no-return-assign": 0, 484 "no-script-url": 2, 485 "no-use-before-define": 2, 486 "no-extra-boolean-cast": 0, 487 "no-unreachable": 1, 488 "comma-dangle": 2, 489 "no-mixed-spaces-and-tabs": 2, 490 "prefer-arrow-callback": 0, 491 "arrow-parens": 0, 492 "arrow-spacing": 0, 493 camelcase: 0, 494 "jsx-quotes": [1, "prefer-double"], 495 "react/display-name": 0, 496 "react/forbid-prop-types": [ 497 2, 498 { 499 forbid: ["any"], 500 }, 501 ], 502 "react/jsx-boolean-value": 0, 503 "react/jsx-closing-bracket-location": 1, 504 "react/jsx-curly-spacing": [ 505 2, 506 { 507 when: "never", 508 children: true, 509 }, 510 ], 511 "react/jsx-indent": ["error", 4], 512 "react/jsx-key": 2, 513 "react/jsx-no-bind": 0, 514 "react/jsx-no-duplicate-props": 2, 515 "react/jsx-no-literals": 0, 516 "react/jsx-no-undef": 1, 517 "react/jsx-pascal-case": 0, 518 "react/jsx-sort-props": 0, 519 "react/jsx-uses-react": 1, 520 "react/jsx-uses-vars": 2, 521 "react/no-danger": 0, 522 "react/no-did-mount-set-state": 0, 523 "react/no-did-update-set-state": 0, 524 "react/no-direct-mutation-state": 2, 525 "react/no-multi-comp": 0, 526 "react/no-set-state": 0, 527 "react/no-unknown-property": 2, 528 "react/prefer-es6-class": 2, 529 "react/prop-types": 0, 530 "react/react-in-jsx-scope": 2, 531 "react/self-closing-comp": 0, 532 "react/sort-comp": 0, 533 "react/no-array-index-key": 0, 534 "react/no-deprecated": 1, 535 "react/jsx-equals-spacing": 2, 536 }, 537 }; 538 539 .eslintignore 540 在根目录下,新增 .eslintignore 文件: 541 542 src/assets 543 .vscode 544 在根目录下新增 .vscode 文件夹,然后新增 .vscode/settings.json 545 546 { 547 "eslint.validate": [ 548 "javascript", 549 "javascriptreact", 550 "typescript", 551 "typescriptreact" 552 ] 553 } 554 Perttier 555 安装依赖 556 $ npm install prettier --save-dev 557 prettier.config.js 558 在根目录下,新增 prettier.config.js 文件: 559 560 module.exports = { 561 // 一行最多 100 字符 562 printWidth: 100, 563 // 使用 4 个空格缩进 564 tabWidth: 4, 565 // 不使用缩进符,而使用空格 566 useTabs: false, 567 // 行尾需要有分号 568 semi: true, 569 // 使用单引号 570 singleQuote: true, 571 // 对象的 key 仅在必要时用引号 572 quoteProps: 'as-needed', 573 // jsx 不使用单引号,而使用双引号 574 jsxSingleQuote: false, 575 // 末尾不需要逗号 576 trailingComma: 'none', 577 // 大括号内的首尾需要空格 578 bracketSpacing: true, 579 // jsx 标签的反尖括号需要换行 580 jsxBracketSameLine: false, 581 // 箭头函数,只有一个参数的时候,也需要括号 582 arrowParens: 'avoid', 583 // 每个文件格式化的范围是文件的全部内容 584 rangeStart: 0, 585 rangeEnd: Infinity, 586 // 不需要写文件开头的 @prettier 587 requirePragma: false, 588 // 不需要自动在文件开头插入 @prettier 589 insertPragma: false, 590 // 使用默认的折行标准 591 proseWrap: 'preserve', 592 // 根据显示样式决定 html 要不要折行 593 htmlWhitespaceSensitivity: 'css', 594 // 换行符使用 lf 595 endOfLine: 'lf' 596 }; 597 stylelint 598 安装依赖 599 $ npm install stylelint stylelint-config-standard stylelint-config-prettier --save-dev 600 stylelint.config.js 601 在根目录下,新增 stylelint.config.js 文件: 602 603 module.exports = { 604 extends: ['stylelint-config-standard', 'stylelint-config-prettier'], 605 ignoreFiles: [ 606 '**/*.ts', 607 '**/*.tsx', 608 '**/*.png', 609 '**/*.jpg', 610 '**/*.jpeg', 611 '**/*.gif', 612 '**/*.mp3', 613 '**/*.json' 614 ], 615 rules: { 616 'at-rule-no-unknown': [ 617 true, 618 { 619 ignoreAtRules: ['extends', 'ignores'] 620 } 621 ], 622 indentation: 4, 623 'number-leading-zero': null, 624 'unit-allowed-list': ['em', 'rem', 's', 'px', 'deg', 'all', 'vh', '%'], 625 'no-eol-whitespace': [ 626 true, 627 { 628 ignore: 'empty-lines' 629 } 630 ], 631 'declaration-block-trailing-semicolon': 'always', 632 'selector-pseudo-class-no-unknown': [ 633 true, 634 { 635 ignorePseudoClasses: ['global'] 636 } 637 ], 638 'block-closing-brace-newline-after': 'always', 639 'declaration-block-semicolon-newline-after': 'always', 640 'no-descending-specificity': null, 641 'selector-list-comma-newline-after': 'always', 642 'selector-pseudo-element-colon-notation': 'single' 643 } 644 }; 645 lint-staged、pre-commit 646 安装依赖 647 $ npm install lint-staged prettier eslint pre-commit --save-dev 648 package.json 649 { 650 // ... 651 "scripts": { 652 "lint:tsx": "eslint --ext .tsx src && eslint --ext .ts src", 653 "lint:css": "stylelint --aei .less .css src", 654 "precommit": "lint-staged", 655 "precommit-msg": "echo 'Pre-commit checks...' && exit 0" 656 }, 657 "pre-commit": [ 658 "precommit", 659 "precommit-msg" 660 ], 661 "lint-staged": { 662 "*.{js,jsx,ts,tsx}": [ 663 "eslint --fix", 664 "prettier --write", 665 "git add" 666 ], 667 "*.{css,less}": [ 668 "stylelint --fix", 669 "prettier --write", 670 "git add" 671 ] 672 } 673 } 674 eslint-webpack-plugin 675 安装依赖 676 $ npm install eslint-webpack-plugin --save-dev 677 script/webpack.config.js 678 const ESLintPlugin = require('eslint-webpack-plugin'); 679 module.exports = { 680 // ... 681 plugins: [new ESLintPlugin()], 682 }; 683 总结 684 搭建这个的过程,也是遇到了不少坑,收获也是蛮多的,希望这个教程能够帮助更多的同学,少采点坑,完整的 React 开发环境可以看这个tristana,
posted on 2021-02-21 18:19 可可爱爱没有脑袋00 阅读(49) 评论(0) 编辑 收藏 举报