前言
前陣子在自己學習React,最開始上手使用的creat-react-app
來創建自己的項目,2版本之后的create-react-app已經支持了很多功能,比如sass、數據mock、typescript支持等等,也升級了相關依賴babel、webpack到一個最新的版本,具體可以參照Create React App 中文文檔,但是它將項目的webpack配置等內容給藏起來了,想要自己配置的話還要npm run eject
才可見,不過對於我這種初學者已經足夠了,但是本着折騰的精神,在掘金看了好多大佬的配置文章,終於折騰出一個自己的項目模板,如果有什么問題或者不對的地方,希望大佬們能及時指出,最后有項目地址~
第二篇生產開發環境配置已經寫完:搭建自己的React+Typescript環境(二)
項目簡介
主要的依賴以及版本
- webpack4+
- babel7+
- typescript3+
- react16.8+
- antd3+
- react-router5+
- eslint5+
初始化項目
- 創建一個目錄,名字按自己喜好來
mkdir react-ts-template cd react-ts-template 復制代碼
- 初始化項目,填寫項目信息
yarn init -y 或者 npm init -y 復制代碼
安裝webpack
yarn add webpack -D 或者 npm i webpack -D
yarn add webpack-cli -D 或者 npm i webpack-cli -D
復制代碼
- webpack也可以全局安裝,不過要注意配置PATH
- webpack4將命令行相關的操作抽離到了webpack-cli中,比如init、migrate、serve等等,不過都沒用過
安裝完畢后在根目錄新建build文件夾,並新建一個webpack.common.js文件,用來存放webpack的公共配置
mkdir build
cd build
touch webapck.common.js
復制代碼
然后在webpack.common.js中簡單的配置入口(entry)跟輸出(output)。
const path = require('path'); module.exports={ entry: path.join(__dirname, '../src/index.js'), output: { filename: 'bundle.js', path: path.join(__dirname, '../dist') } } 復制代碼
接着在根目錄下再新建src文件夾,用來存放主要代碼,並新建index.js,隨便寫點東西。
console.log('Hello World'); 復制代碼
在package.json中加入一個腳本,並在控制台中運行它npm run build
"scripts": {
"build": "webpack --config build/webpack.common.js"
}
復制代碼
之后會發現生成了一個dist文件夾,並且還有一個bundle.js,同時控制台還會有報錯
WARNING in configuration The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment. You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/ 復制代碼
webpack4中提供了 mode
配置選項,告知 webpack 使用相應模式的內置優化,上面這個警告寫着如果不提供mode,webpack將會使用production
模式。我們把scripts修改一下。
"scripts": {
"build": "webpack --config build/webpack.common.js --mode production"
}
復制代碼
這樣的webpack簡單的打包功能就有了。
Bable
Bable這里使用的是7版本,與之前版本不同的是安裝依賴時的包名,像babel-core、babel-preset-env、babel-polyfill等,名字已經更換成了@babel/core、@babel/preset-env、@babel/polyfill,這里先安裝主要的包。
yarn add @babel/core @babel/preset-env @babel/preset-react babel-loader -D
復制代碼
然后在根目錄下新建一個babel.config.js,這個配置文件跟.babelrc.js是有區別的,根據官網來看babel.config.js是項目級別的一個配置,詳細信息可以參照官網 Config Files,在其中添加如下內容:
module.exports = { presets: [ '@babel/preset-env', '@babel/preset-react' ], plugins: [] } 復制代碼
修改webpack.common.js,增加 js 文件的 loader 配置,之后還會改。
module: { rules: [{ test: /\.js$/, use: ['babel-loader'], include: path.join(__dirname, '../src') }] } 復制代碼
React
接下來加入React,也是最重要的部分。
yarn add react react-dom
復制代碼
修改 src/index.js 中的內容
import React from 'react'; import ReactDom from 'react-dom'; ReactDom.render(<div>Hello React!</div>, document.getElementById('root')); 復制代碼
然后在根目錄下新建一個public文件夾,並在里面新建一個index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>React-TS-Tempalte</title> </head> <body> <div id="root"></div> </body> </html> 復制代碼
想要 webpack 能以這個html為模板,還需要一個html-webpack-plugin
插件,安裝它yarn add html-webpack-plugin -D
並在webpack.common.js中增加如下配置,這也是用到的第一個插件:
const HtmlWebpackPlugin = require('html-webpack-plugin'); ... plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: 'public/index.html', inject: true }) ] 復制代碼
讓我們打包后打開dist下的index.html看看效果,成功地展示了Hello React。

開發環境配置
接下來安裝 webpack-dev-server
來啟動一個簡單的服務器。
yarn add webpack-dev-server -D
復制代碼
修改webpack.common.config.js,增加webpack-dev-server的配置。
devServer: {
host: 'localhost', port: 3000, historyApiFallback: true, overlay: { //當出現編譯器錯誤或警告時,就在網頁上顯示一層黑色的背景層和錯誤信息 errors: true }, inline: true, hot: true } 復制代碼
接下來需要在package.json中增加一個script
"scripts": { "dev": "webpack-dev-server --config build/webpack.common.js --mode development --open" } 復制代碼
在控制台中輸入npm run dev
,便可以在http://localhost:3000
中看到啟動的項目。
Typescript
下面加入Typescript依賴,關鍵的依賴包就是 typescript,不過還需要安裝對應的types:@types/react、@types/react-dom。
yarn add typescript @types/react @types/react-dom -D
復制代碼
接下來需要把之前的 js、jsx 文件替換成對應的 ts、tsx,同時還需要對應的loader,可以使用 ts-loader
以及之前安裝過的 babel-loader
,這里使用之前安裝的 babel-loader
,在webpack.common.js中添加配置:
rules: [
{
test: /\.(j|t)sx?$/, include: [resolve('../src')], use: [ { loader: 'babel-loader' } ], // 排除node_modules底下的 exclude: /node_modules/ } ] 復制代碼
修改 webpack 的入口配置
module.exports={ entry: path.join(__dirname, '../src/index.tsx') } 復制代碼
不要忘記把src下的index.js改成index.tsx
配置tsconfig.json,這個文件也是使用ts時很關鍵的一個文件,下面是官網的推薦配置。
{
"compilerOptions": {
/* Basic Options */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": [
"dom",
"dom.iterable",
"esnext"
], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"sourceMap": true, /* Generates corresponding '.map' file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"strict": true, /* Enable all strict type-checking options. */
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
},
"include": [
"src"
],
"exclude": [
"node_modules"
]
}
復制代碼
CSS配置
這里我們需要用到 style-loader
、css-loader
,先安裝它們
- css-loader使你能夠使用類似@import 和 url(...)的方法實現 require()的功能;
- style-loader將所有的計算后的樣式加入頁面中; 二者組合在一起使你能夠把樣式表嵌入webpack打包后的JS文件中。
yarn add style-loader css-loader -D
復制代碼
然后在webpack.common.js中添加相應的規則
{
test: /\.css$/, // 正則匹配文件路徑 exclude: /node_modules/, use: [ // 注意loader生效是從下往上的 'style-loader', 'css-loader' ] } 復制代碼
接着在webpack.common.js中配置resolve.extensions,來自動解析確定的擴展。
resolve: {
extensions: ['.ts', '.tsx', '.js', 'jsx'] } 復制代碼
在根目錄下新建一個index.css,以及App.tsx,並在index.tsx中引入它們
// index.css
.app {
background-color: red;
}
// App.tsx
import * as React from 'react' class App extends React.Component { render() { return ( <div className="app"> Hello React </div> ) } } export default App // index.tsx import React from 'react' import ReactDOM from 'react-dom' import App from './App' import './index.css' ReactDOM.render(<App />, document.getElementById('root')) 復制代碼
啟動后便可以看到設置的紅色背景
加入Sass
如果想要在編寫樣式時使用sass的語法,就需要安裝相應的loader。
yarn add sass-loader node-sass -D
復制代碼
注意:node-sass安裝時可能會遇到網絡問題,需要使用淘寶鏡像源。
安裝完成后在webpack.common.js中加入 .scss 文件的規則
{
test: /\.scss$/, include: path.join(__dirname, '../src'), use: [ 'style-loader', 'css-loader', 'sass-loader' ] } 復制代碼
接下來我們把根目錄下的index.css改成index.scss,不要忘記index.tsx中引入的文件后綴也要修改,項目啟動后發現可以成功解析scss文件。
配置公共sass屬性
既然已經可以使用sass進行更加簡便的css代碼編寫,那么我們也可以將常用的一些樣式代碼和sass變量寫入公共文件中,當使用的時候就可以直接引入使用。
在src目錄下新建styles文件夾,然后新建一個var.scss文件用於存放樣式變量。 之后在var.scss文件里寫入一個顏色變量和一個樣式:
$red: red; @mixin ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } 復制代碼
然后在剛才的index.scss文件中引入它。
@import './styles/var.scss'; .app{ background: $red; .aps { width: 50px; @include ellipsis; } } 復制代碼
這樣配置之后還存在一個優化上的問題,如果需要在不同的層級引入var.scss就要根據每個文件夾的路徑相對來引入非常麻煩,那么我們能否做到只需要@import var.scss就行呢?答案是可以的,我們可以通過配置loader的屬性includePaths進行路徑優化,修改webpack.common.js。
{
test: /\.scss$/, include: path.join(__dirname, '../src'), use: [ 'style-loader', 'css-loader', { loader: 'sass-loader', options: { includePaths: [path.join(__dirname, '../src/styles')] } } ] } 復制代碼
這樣之后我們在引入的時候只需要寫文件名稱即可。
@import 'var.scss'; 復制代碼
加入PostCSS
什么是PostCSS呢?借用官方的話:
PostCSS 是一個允許使用 JS 插件轉換樣式的工具。 這些插件可以檢查(lint)你的 CSS,支持 CSS Variables 和 Mixins, 編譯尚未被瀏覽器廣泛支持的先進的 CSS 語法,內聯圖片,以及其它很多優秀的功能。
它提供了很多常用的插件
- 提前使用先進的 CSS 特性
- autoprefixer 添加了 vendor 瀏覽器前綴,它使用 Can I Use 上面的數據。
- postcss-preset-env 允許你使用未來的 CSS 特性。
- 更佳的 CSS 可讀性
- precss 囊括了許多插件來支持類似 Sass 的特性,比如 CSS 變量,套嵌,mixins 等。
- 圖片和字體
- postcss-assets 可以插入圖片尺寸和內聯文件。
- postcss-sprites 能生成雪碧圖。
...還有很多,具體可以查看PostCSS中文Readme
這里主要用autoprefixer,首先安裝postcss-loader
yarn add postcss-loader autoprefixer -D
復制代碼
之后在根目錄下新建一個postcss.config.js文件,並寫入:
module.exports = { plugins: [ require('autoprefixer') ] } 復制代碼
最后需要在webpack.common.js的樣式相關插件的 css-loader 之后加上配置,以scss為例
{
test: /\.scss$/, include: path.join(__dirname, '../src'), use: [ 'style-loader', 'css-loader', 'postcss-loader', // 加了這一行 { loader: 'sass-loader', options: { includePaths: [path.join(__dirname, '../src/styles')] } } ] } 復制代碼
隨便寫點樣式,然后在谷歌控制台可以發現,會自動幫你添加 -webkit-
的前綴。
注意如果使用了 postcss-preset-env
這個的話,它會自動安裝 autoprefixer
,並且配置了它,就不再需要配置 autoprefixer
。
const postcssPresetEnv = require('postcss-preset-env'); module.exports = { plugins: [ postcssPresetEnv(/* pluginOptions */) ] } 復制代碼
CSS Modules優化
CSS Modules 是為了加入局部作用域和模塊依賴,這里我沒加入它,可以代替它的方案也有,比如scoped,以及bem命名方式等,這里我選擇了bem命名方法,掘金也有關於它的介紹,可以去看看。
圖片字體等資源加載
首先安裝處理這類資源的加載器
yarn add url-loader file-loader -D
復制代碼
然后在webpack.common.js中加入相關的規則配置
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use: [ { loader: 'url-loader', options: { //1024 == 1kb //小於10kb時打包成base64編碼的圖片否則單獨打包成圖片 limit: 10240, name: path.join('img/[name].[hash:7].[ext]') } }] }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, use: [{ loader: 'url-loader', options: { limit: 10240, name: path.join('font/[name].[hash:7].[ext]') } }] } 復制代碼
ESlint
關於typescript代碼的規范校驗,可選的有tslint和eslint,不過最近官方也推薦往eslint上轉了,所以我這里使用eslint,安裝相關依賴。
yarn add eslint eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser
復制代碼
如果不使用 hook
的話,就不用裝 eslint-plugin-react-hooks
這個插件了
在根目錄下新建.eslintrc.js,並寫入配置:
module.exports = { extends: [ "eslint:recommended", "plugin:react/recommended" ], parserOptions: { "ecmaVersion": 2019, "sourceType": "module" }, env: { node: true, browser: true, commonjs: true, es6: true }, parser: '@typescript-eslint/parser', plugins: [ "@typescript-eslint", "react-hooks" ], globals: { // 這里填入你的項目需要的全局變量 // 這里值為 false 表示這個全局變量不允許被重新賦值,比如: // React: false, // ReactDOM: false }, settings: { react: { pragma: "React", version: "detect" } }, rules: { // 這里填入你的項目需要的個性化配置,比如: // // // @fixable 一個縮進必須用兩個空格替代 semi: ['error', 'never'], 'no-console': 'off', 'no-unused-vars': [ 'warn', { vars: 'all', args: 'none', caughtErrors: 'none' } ], 'max-nested-callbacks': 'off', 'react/no-children-prop': 'off', 'typescript/member-ordering': 'off', 'typescript/member-delimiter-style': 'off', 'react/jsx-indent-props': 'off', 'react/no-did-update-set-state': 'off', "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", indent: [ 'off', 2, { SwitchCase: 1, flatTernaryExpressions: true } ] } } 復制代碼
用 VS Code 開發時,應該還需要配置settings.json
"eslint.autoFixOnSave": true, "eslint.validate": [ "javascript", "javascriptreact", { "language": "html", "autoFix": true }, { "language": "vue", "autoFix": true }, { "language": "typescript", "autoFix": true }, { "language": "typescriptreact", "autoFix": true }, ] 復制代碼
AntDesign
antd是阿里家的一款UI組件庫,官方文檔中關於如何引入使用講的很清楚,我們來配置一下,先安裝需要的依賴,babel-plugin-import
用於按需引入:
yarn add antd
yarn add babel-plugin-import less less-loader -D
復制代碼
在babel.config.js中增加配置
plugins: [
...
['import', { libraryName: 'antd', libraryDirectory: 'lib', style: true }] ] 復制代碼
還需要在webpack.common.js中配置less規則
{
// for ant design test: /\.less$/, include: resolve('../node_modules'), use: [ 'style-loader', 'css-loader', 'postcss-loader', { loader: 'less-loader', options: { javascriptEnabled: true, modifyVars: theme } } ] } 復制代碼
注意 modifyVars: theme
這個是根據官網來的 自定義主題,需要新建一個 theme.js,這個文件的名字自己定義,這里修改了一下主要顏色,以及 border-radius-base
的值,還有很多配置具體可以查看官網。
module.exports = { 'primary-color': 'black', 'border-radius-base': '10px' } 復制代碼
之后便可以按照官網實例愉快的使用了。不過打包之后存在一個問題,icons.js占了很大的一部分,這里使用github上的大佬給出的解決方案。
在src下新建一個icons.ts,里面只寫用到的icon
export { default as DownOutline } from '@ant-design/icons/lib/outline/DownOutline' 復制代碼
然后配置別名,這里就將在ts中使用別名的方式一塊介紹了。在 webpack.common.js 中的 resolve
項中增加下面配置,跟icon有關的是第二行。
alias: { '@': resolve('../src'), "@ant-design/icons/lib/dist$": resolve('../src/icons.ts'), '@components': resolve('../src/components'), '@img': resolve('../src/assets/img') } 復制代碼
還需要在tsconfig.json中增加配置:
"paths": { "@/*": ["src/*"], "@ant-design/icons/lib/dist$": ["src/icons.js"], "@components/*": ["src/components/*"], "@img/*": ["src/assets/img/*"] } 復制代碼
更新babel配置
之前加了 typescript 等依賴,現在來更新一下 babel 的配置,來支持我們后面可能會用到的功能,比如裝飾器以及路由的動態引入。
yarn add @babel/preset-typescript @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-syntax-dynamic-import -D
復制代碼
修改 babel.config.js
module.exports = { presets: [ '@babel/preset-env', '@babel/preset-typescript', '@babel/preset-react' ], plugins: [ ['import', { libraryName: 'antd', libraryDirectory: 'lib', style: true }], ['@babel/plugin-proposal-decorators', { legacy: true }], ['@babel/plugin-proposal-class-properties', { loose: true }], '@babel/plugin-syntax-dynamic-import' ] } 復制代碼
React-router
router使用最新的5版本,然后路由懶加載使用官方例子中的loadable,首先還是安裝依賴
yarn add react-router-dom
yarn add @loadable/component @types/loadable__component @types/react-router-dom -D
復制代碼
讓我們在 App.tsx 中使用它們
import * as React from 'react' import { HashRouter as Router, Route, Link } from "react-router-dom" import loadable from '@loadable/component' const HomeComponent = loadable(() => import(/* webpackChunkName: "home" */ './views/Home')) const AboutComponent = loadable(() => import(/* webpackChunkName: "about" */ './views/About')) class App extends React.Component { render() { return ( <div className="app"> <Router> <ul> <li> <Link to="/">To Home</Link> </li> <li> <Link to="/about">To About</Link> </li> </ul> <Route exact path='/' component={HomeComponent}></Route> <Route path='/about' component={AboutComponent}></Route> </Router> <p className="aps">hahahaahhahhahahaha</p> </div> ) } } export default App 復制代碼
更多
到這里基本的功能已經具備了,接下來還需要一些優化以及拆分開發和生產環境的配置,篇幅有點長了,放到下一篇文章里寫吧。
最后附上地址 項目地址,如果有不對的地方希望各位指出,感謝。
作者:GLaDOS
鏈接:https://juejin.im/post/5d0ccc9ff265da1b934e0a44
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。