$ 如何安裝 ?
# https://git-scm.com/downloads
$ 如何使用 ?
# https://git-scm.com/doc || git --help
複製代碼
⬆ back to topjavascript
.gitignore
有時,有些文件不但願
Git
簽入GitHub
。.gitignore
配置文件能夠告訴Git
忽略哪些文件。css
$ touch .gitignore # starter/.gitignore
# dependencies
/node_modules
# testing
/coverage
# production
/build
dist
# misc
.DS_Store
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.vscode
複製代碼
⬆ back to tophtml
$ node 是什麼 ?
# http://nodejs.cn/
$ 如何安裝 ?
# http://nodejs.cn/download/
$ npm 是什麼 ?
# https://docs.npmjs.com/about-npm/
$ npm 如何使用 ?
# 安裝 Node.js 時附帶安裝了 npm || npm -v
$ 建立包管理配置文件 package.json
# https://docs.npmjs.com/creating-a-package-json-file
$ package.json 文件中的要求 ?
# https://docs.npmjs.com/files/package.json.html
$ package-lock.json 是什麼 ?
# https://docs.npmjs.com/files/package-lock.json.html
$ yarn 是什麼?
# https://yarn.bootcss.com/
$ yarn 如何安裝 ?
# https://yarn.bootcss.com/docs/install/#mac-stable
$ yarn 如何使用 ?
# https://yarn.bootcss.com/docs/
$ 初始化項目
# mkdir starter && npm init
複製代碼
初始工程目錄
與 package.json
的信息 ✅工程目錄vue
└── starter
├── README.md
└── package.json
複製代碼
package.jsonjava
{
"name": "starter",
"version": "1.0.0",
"description": "List of engineering builds for web applications",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/cllemon/starter.git"
},
"keywords": [
"javascript",
"typescript",
"react"
],
"author": "cllemon",
"license": "MIT",
"bugs": {
"url": "https://github.com/cllemon/starter/issues"
},
"homepage": "https://github.com/cllemon/starter#readme"
}
複製代碼
注意:如下描述中全部的包安裝都採用
yarn
命令node
⬆ back to topreact
EditorConfig
能夠幫助開發者在不一樣的編輯器和IDE
之間定義和維護一致的代碼風格。webpack
EditorConfig is awesome: editorconfig.orgios
$ touch .editoorconfig
# starter/.editoorconfig
root = true # 代表是最頂層的配置文件,發現設爲 true 時,纔會中止查找.editorconfig 文件。
[*]
charset = utf-8
indent_style = space # tab 爲 hard-tabs,space 爲 soft-tabs。
indent_size = 2 # 規定每級縮進的列數和 soft-tabs 的寬度(空格數)。若是設定爲 tab,則會使用 tab_width 的值。
end_of_line = lf # 定義換行符,支持 lf(UNIX/Linux採用換行符 LF 表示下一行)、cr(MAC OS系統)則採用回車符 CR 表示下一行) 和 crlf。
insert_final_newline = true # 設爲 true 代表使文件以一個空白行結尾,false 反之
trim_trailing_whitespace = true # 設爲 true 表示會除去換行行首的任意空白字符,false 反之。
[*.md] # 校驗 markdown 文檔
insert_final_newline = false
trim_trailing_whitespace = false
複製代碼
browserslist
是什麼?
用於在不一樣前端工具之間共享目標瀏覽器和
Node.js
版本的配置。例如Autoprefixer
,Stylelint
和babel-preset-env
。
browserslist
配置方式
當您將如下內容添加到
package.json
或 .browserslistrc配置文件中時,全部工具都會自動找到目標瀏覽器:
# package.json
{
"browserslist": {
"production": [ // 生產環境配置
">0.2%", // 支持市場份額大於 1% 的瀏覽器。
"not dead", // not(邏輯非)對 dead 取反,而瀏覽器被認爲是 dead 條件是:最新的兩個版本中發現其市場份額已經低於 0.5% 而且 24 個月內沒有官方支持和更新。
"not op_mini all" // OperaMini or op_mini for Opera Mini.
],
"development": [ // 開發環境配置
"last 1 chrome version", // 瀏覽器版本查詢範圍, chrome 最近的一個版本
"last 1 firefox version",
"last 1 safari version"
]
}
}
或🔥
# .browserslistrc
$ touch .browserslist
[production]
> .2%
not dead
not op_mini all
[development]
last 1 chrome version
last 1 firefox version
last 1 safari version
複製代碼
本質上,
webpack
是一個現代JavaScript
應用程序的靜態模塊打包工具。當webpack
處理應用程序時,它會在內部構建一個 依賴圖(dependency graph
),此依賴圖會映射項目所需的每一個模塊,並生成一個或多個bundle
。
安裝與建立基本文件
$ mkdir src # 建立存放核心代碼文件夾
$ cd src && touch index.js # 建立入口文件
$ yarn add -D webpack # 安裝最新版本 webpack^4.41.2
$ yarn add -D webpack-cli # 安裝 webpack v4+ 版本,所需的 webpack-cli^3.3.9
$ cd .. && touch webpack.config.js # 根目錄,建立 webpack 基本配置文件
複製代碼
工程目錄
└── starter
+ ├── node_modules
+ ├── src
+ │ └── index.js
+ ├── webpack.config.js
├── package.json
└── README.md
複製代碼
A declarative, efficient, and flexible JavaScript library for building user interfaces.
安裝與建立基本文件
$ yarn add react # 安裝 react^16.10.2
$ yarn add react-dom # 安裝 react-dom^16.10.2
$ mkdir public # 新建公共資源文件夾
$ cd public && touch index.html # 新建 html 文件
$ copy favicon.ico # 添加 網頁圖標 文件
$ cd .. # 回到根目錄
複製代碼
編寫 index.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="This is a react application built from scratch with JavaScript, away from the cli tool." />
<title>Starter</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
複製代碼
編寫 index.js 文件
import React from 'react';
import ReactDom from 'react-dom';
const App = () => <h1>Hello, world!</h1>
ReactDom.render(<App />, document.getElementById('root')); 複製代碼
注意:因爲瀏覽器不支持最新的 JavaScript 語法和 react jsx 的語法解析,因此咱們須要一個編譯器幫助咱們。
Babel 是一個工具鏈,主要用於在舊的瀏覽器或環境中將 ECMAScript 2015+ 代碼轉換爲向後兼容版本的 JavaScript 代碼。
babel 安裝
$ yarn add -D @babel/core # Babel 編譯器核心模塊
$ yarn add -D @babel/preset-env # 是一個智能預設,它使您可使用最新的JavaScript,而無需微觀管理目標環境所需的語法轉換
$ yarn add -D @babel/preset-react # react 智能預設, 包含了解析 jsx 等插件
$ yarn add -D babel-loader # Babel loader for webpack 該軟件包容許使用 Babel 和 webpack 來轉譯 JavaScript 文件。
$ touch .babelrc # 新建 babel 配置文件
複製代碼
babel 配置
// .babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
}
複製代碼
編寫 webpack 配置
// starter/webpack.config.js
const path = require('path');
module.exports = function() {
const baseConfig = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
]
}
};
return baseConfig;
};
複製代碼
修改 package.json 添加 webpack 命令, 快捷運行
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
+ "build": "webpack --color --progress"
}
}
複製代碼
修改 index.html 引入打包以後的 bundle.js 文件
...
<div id="root"></div>
+ <script src="../dist/bundle.js"></script>
...
複製代碼
運行項目
$ yarn build # 打包文件
Hash: 6e4adf36d533e9d646c0
Version: webpack 4.41.2
Time: 693ms
Built at: 2019-10-19 11:41:22
Asset Size Chunks Chunk Names
bundle.js 1.09 MiB main [emitted] main
Entrypoint main = bundle.js
[./src/index.js] 233 bytes {main} [built]
# 瀏覽器 打開 index.html 查看效果
複製代碼
工程目錄
└── starter
+ ├── dist
+ │ └── bundle.js
├── node_modules
+ ├── public
+ │ ├── favicon.ico
+ │ └── index.html
├── src
│ └── index.js
├── webpack.config.js
├── package.json
├── README.md
+ └── yarn.lock
複製代碼
webpack-dev-server 爲你提供了一個簡單的 web server,而且具備 live reloading(實時從新加載) 功能。
安裝
$ yarn add -D webpack-dev-server # 用於快速開發應用程序
複製代碼
添加相應配置
// starter/webpack.config.js
const path = require('path');
module.exports = function() {
const baseConfig = {
devtool: 'inline-source-map', // 控制是否生成,以及如何生成 source map
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
]
},
+ devServer: {
+ contentBase: path.resolve(__dirname, 'public'), // 告訴服務器從哪一個目錄中提供內容
+ historyApiFallback: true, // 啓用當使用 HTML5 History API 時,任意的 404 響應均可能須要被替代爲 index.html。
+ compress: true, // 一切服務都啓用 gzip 壓縮
+ open: true, // 告訴 dev-server 在 server 啓動後打開瀏覽器
+ port: 3000, // 指定要監聽請求的端口號
+ stats: 'errors-only', // 精確控制要顯示的 bundle 信息 (在 bundle 中只顯示錯誤)
+ }
};
return baseConfig;
};
複製代碼
修改 package.json 添加 webpack 命令, 快捷運行
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --color --progress",
+ "server": "webpack-dev-server --color --progress"
}
}
複製代碼
--color
: 啓用/禁用控制檯的彩色輸出;--progress
: 將運行進度輸出到控制檯。
修改 index.html 主文件 bundle.js 路徑
...
<div id="root"></div>
- <script src="../dist/bundle.js"></script>
+ <script src="bundle.js"></script>
...
複製代碼
運行項目
$ yarn server
# 結果:
$ webpack-dev-server --color --progress
10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.
複製代碼
打開
http://localhost:3000/
將顯示Hello, world!
; 修改src/index.js
將會刷新瀏覽器實時更新修改。Try it!
存在問題或待改進提高點
webpack.config.js
有些配置咱們只但願在開發環境有,而在生產環境應有其特定配置)帶着這些問題,繼續吧!👍
安裝
$ yarn add -D cross-env # Cross platform setting of environment scripts
複製代碼
修改 package.json webpack 命令
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
- "build": "webpack --color --progress",
+ "build": "cross-env NODE_ENV=production webpack --color --progress",
- "server": "webpack-dev-server --color --progress"
+ "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress"
}
}
複製代碼
爲 webpack.config.js 添加相應配置
// starter/webpack.config.js
const path = require('path');
+ const IS_PROD = process.env.NODE_ENV === 'production';
module.exports = function() {
const baseConfig = {
+ mode: IS_PROD ? 'production' : 'development',
- devtool: 'inline-source-map', // 控制是否生成,以及如何生成 source map
+ devtool: IS_PROD ? false : 'inline-source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
]
},
- devServer: {
- contentBase: path.resolve(__dirname, 'public'), // 告訴服務器從哪一個目錄中提供內容
- historyApiFallback: true, // 啓用當使用 HTML5 History API 時,任意的 404 響應均可能須要被替代爲 index.html。
- compress: true, // 一切服務都啓用 gzip 壓縮
- open: true, // 告訴 dev-server 在 server 啓動後打開瀏覽器
- port: 3000, // 指定要監聽請求的端口號
- stats: 'errors-only', // 精確控制要顯示的 bundle 信息 (在 bundle 中只顯示錯誤)
- }
};
+ if (!IS_PROD) {
+ baseConfig.devServer = {
+ contentBase: path.resolve(__dirname, 'public'), // 告訴服務器從哪一個目錄中提供內容
+ historyApiFallback: true, // 啓用當使用 HTML5 History API 時,任意的 404 響應均可能須要被替代爲 index.html。
+ compress: true, // 一切服務都啓用 gzip 壓縮
+ open: true, // 告訴 dev-server 在 server 啓動後打開瀏覽器
+ port: 3000, // 指定要監聽請求的端口號
+ stats: 'errors-only', // 精確控制要顯示的 bundle 信息 (在 bundle 中只顯示錯誤)
+ };
+ }
return baseConfig;
};
複製代碼
模塊熱替換(hot module replacement 或 HMR)是 webpack 提供的最有用的功能之一。它容許在運行時更新全部類型的模塊,而無需徹底刷新。
爲 webpack.config.js 添加相應配置
// starter/webpack.config.js
const path = require('path');
+ const webpack = require('webpack');
const IS_PROD = process.env.NODE_ENV === 'production';
module.exports = function() {
const baseConfig = {
mode: IS_PROD ? 'production' : 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
]
},
+ plugins: []
};
if (!IS_PROD) {
baseConfig.devServer = {
contentBase: path.resolve(__dirname, 'public'), // 告訴服務器從哪一個目錄中提供內容
historyApiFallback: true, // 啓用當使用 HTML5 History API 時,任意的 404 響應均可能須要被替代爲 index.html。
compress: true, // 一切服務都啓用 gzip 壓縮
open: true, // 告訴 dev-server 在 server 啓動後打開瀏覽器
port: 3000, // 指定要監聽請求的端口號
stats: 'errors-only', // 精確控制要顯示的 bundle 信息 (在 bundle 中只顯示錯誤)
+ hot: true // 啓用 webpack 的 模塊熱替換 功能
};
+ baseConfig.plugins.concat([
+ new webpack.HotModuleReplacementPlugin() // 熱替換模塊插件
+ ]);
}
return baseConfig;
};
複製代碼
修改 src/index.js 文件
- import React from 'react';
+ import React, { useState } from 'react';
import ReactDom from 'react-dom';
- const App = () => <h1>Hello, world!</h1>;
+ const App = () => {
+ const [title, setTitle] = useState('hello, world!');
+ const reversedTitle = () =>
+ setTitle(
+ title
+ .split('')
+ .reverse()
+ .join('')
+ );
+ return (
+ <div>
+ <h1>{ title }</h1>
+ <button type='button' onClick={reversedTitle}>
+ reversed title
+ </button>
+ </div>
+ );
+ };
+ if (module.hot) {
+ module.hot.accept();
+ }
ReactDom.render(<App />, document.getElementById('root'));
複製代碼
運行項目
$ yarn server
# 結果:
$ cross-env NODE_ENV=development webpack-dev-server --color --progress
10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.
# 瀏覽器 console
[HMR] Waiting for update signal from WDS... log.js:24
[WDS] Hot Module Replacement enabled. client:48
[WDS] Live Reloading enabled. client:52
複製代碼
打開
http://localhost:3000/
修改src/index.js
實現了未刷新瀏覽器更新修改。Try it!
存在問題或待改進提高點
帶着這個問題,繼續吧!✈️
實時調整React組件。
說明
安裝
$ yarn add react-hot-loader
$ yarn add @hot-loader/react-dom # 替換了相同版本的 react-dom 軟件包,但附加了一些補丁以支持熱重裝。
複製代碼
將 "react-hot-loader/babel"
添加到您的 .babelrc
中
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
+ "plugins": ["react-hot-loader/babel"]
}
複製代碼
重置 react-dom
兼容 hooks
...
moduele.exports = function () {
...
+ resolve: {
+ alias: {
+ 'react-dom': '@hot-loader/react-dom' // react-hot-loader 兼容 hook 寫法
+ }
+ },
...
}
...
複製代碼
修改 src/index.js 主文件,將根組件標記爲 hot-exported
+ import { hot } from 'react-hot-loader';
import React, { useState } from 'react';
import ReactDom from 'react-dom';
- const App = () => {
+ const App = hot(module)(() => {
const [title, setTitle] = useState('hello, world!');
const reversedTitle = () =>
setTitle(
title
.split('')
.reverse()
.join('')
);
return (
<div>
<h1>{title}</h1>
<button type='button' onClick={reversedTitle}>
reversed title!
</button>
</div>
);
- };
+ });
- if (module.hot) {
- module.hot.accept();
- }
ReactDom.render(<App />, document.getElementById('root'));
複製代碼
運行項目
$ yarn server
# 結果:
$ cross-env NODE_ENV=development webpack-dev-server --color --progress
10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.
ℹ 「wdm」: Compiling...
ℹ 「wdm」: Compiled successfully.
# 瀏覽器 console
[HMR] Waiting for update signal from WDS... log.js:24
[WDS] App hot update... reloadApp.js:19
[HMR] Checking for updates on the server... log.js:24
[HMR] Updated modules: log.js:24
[HMR] - ./src/index.js log.js:24
[HMR] App is up to date. log.js:24
複製代碼
打開
http://localhost:3000/
, 點擊reversed title
而後修改src/index.js
實現了未刷新瀏覽器保留組件狀態的更新修改。Try it!
階段結語
樣式是前端組件重要組成部分,而 Sass 讓 CSS 語言更強大、優雅;有助於保持大型樣式表結構良好。
注意:本項目引入 sass ,固然你也能夠不引入或者引入其它,如:less、stylus。
安裝
$ yarn add -D node-sass # Node-sass是一個庫,提供了 Node.js 與 LibSass(流行的樣式表預處理器Sass的C版本)的綁定。 它使您可以以驚人的速度經過鏈接中間件自動將 .scss 文件本地編譯爲 css
$ yarn add -D sass-loader # Compiles Sass to CSS
$ yarn add -D css-loader # The css-loader interprets @import and url() like import/require() and will resolve them.
$ yarn add -D style-loader # Inject CSS into the DOM.
複製代碼
注:sass基於Ruby語言開發而成,所以安裝sass前須要安裝Ruby。(注:mac下自帶Ruby無需在安裝Ruby!)
爲何須要 node-sass : 由於 sass-loader 的 peerDependencies 聲明瞭其依賴 node-sass,因此須要預裝,不然警告。
配置:修改 webpack.config.js 增長css/sass解析能力
...
moduele.exports = function () {
...
module: {
rules: [
...
+ {
+ test: /\.(sa|sc|c)ss$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'style-loader'
+ },
+ {
+ loader: 'css-loader',
+ options: {
+ sourceMap: !IS_PROD
+ }
+ },
+ {
+ loader: 'sass-loader',
+ options: {
+ sourceMap: !IS_PROD
+ }
+ }
+ ]
+ }
]
}
...
}
...
複製代碼
新增 src/index.scss 和 style/global.css 樣式文件
$ cd src && touch index.scss
$ mkdir style && cd style
$ touch global.css && touch reset.css
複製代碼
// src/style/reset.css
# reset 重置瀏覽器初始樣式,具體樣式參見項目 src/style/reset.css
// src/style/global.css
@import url('./reset.css');
// src/index.scss
.app {
background-color: red;
}
複製代碼
修改 src/index.js 導入樣式表
import { hot } from 'react-hot-loader';
import React, { useState } from 'react';
import ReactDom from 'react-dom';
+ import './style/global.css';
+ import './index.scss';
const App = hot(module)(() => {
const [title, setTitle] = useState('hello, world!');
const reversedTitle = () =>
setTitle(
title
.split('')
.reverse()
.join('')
);
return (
- <div>
+ <div className='app'>
<h1>{title}</h1>
<button type='button' onClick={reversedTitle}>
reversed title!
</button>
</div>
);
});
ReactDom.render(<App />, document.getElementById('root'));
複製代碼
運行項目
$ yarn server
# 結果:
$ cross-env NODE_ENV=development webpack-dev-server --color --progress
10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.
複製代碼
打開
http://localhost:3000/
, 如你所寫,出現一個紅色背景。Try it!
問題與改進點🤔
CSS
文件而且添加瀏覽器前綴到 CSS
內容裏;postcss/autoprefixer
css-modules
去解決這個問題。固然你也能夠採用嚴格命名規範繞開這個問題,如:BEM。那繼續吧!💪
安裝
$ yarn add - D postcss-loader # 用於webpack的Loader以使用PostCSS處理CSS
$ yarn add -D autoprefixer # Parse CSS and add vendor prefixes to rules by Can I Use
複製代碼
新建 postcss 配置文件
$ touch postcss.config.js # 新建 postcss 配置文件
# starter/postcss.config.js 添加 autoprefixer 插件
module.exports = {
plugins: {
autoprefixer: {},
}
};
複製代碼
添加 webpack postcss 配置
...
moduele.exports = function () {
...
module: {
rules: [
...
{
- test: /\.(sa|sc|c)ss$/,
+ test: /\.(sa|sc)ss$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
+ options: {
+ sourceMap: !IS_PROD,
+ importLoaders: 2, // 啓用/禁用或設置在CSS加載程序以前應用的加載程序的數量
+ modules: {
+ context: path.resolve(__dirname, 'src'), // 容許爲本地標識符名稱從新定義基本的加載程序上下文。
+ localIdentName: '[name]__[local]-[hash:base64:5]' // 使用 localIdentName 查詢參數配置生成類名
+ }
+ }
},
+ {
+ loader: 'postcss-loader'
+ }
{
loader: 'sass-loader',
options: {
sourceMap: !IS_PROD
}
}
]
},
+ {
+ test: /\.css$/,
+ exclude: /node_modules/,
+ use: [
+ 'style-loader',
+ {
+ loader: 'css-loader',
+ options: {
+ sourceMap: !IS_PROD
+ }
+ },
+ 'post-loader'
+ ]
+ }
]
}
...
}
...
複製代碼
postcss
: 一個用JavaScript
轉換CSS
的工具
css-loader
提供CSS
模塊及其配置
修改 src/index.js 類名寫法
import { hot } from 'react-hot-loader';
import React, { useState } from 'react';
import ReactDom from 'react-dom';
import './style/global.css';
- import './index.scss';
+ import styles from './index.scss';
const App = hot(module)(() => {
const [title, setTitle] = useState('hello, world!');
const reversedTitle = () =>
setTitle(
title
.split('')
.reverse()
.join('')
);
return (
- <div className='app'>
+ <div className={styles.app}>
<h1>{title}</h1>
<button type='button' onClick={reversedTitle}>
reversed title!
</button>
</div>
);
});
ReactDom.render(<App />, document.getElementById('root'));
複製代碼
運行項目
$ yarn server
# 結果
$ cross-env NODE_ENV=development webpack-dev-server --color --progress
10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/gt/LEMON/starter/public
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.
複製代碼
打開
http://localhost:3000/
查看,是否如你所寫!
yarn build
$ yarn build
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: 4f40eeb2a231c73dacd9
Version: webpack 4.41.2
Time: 4142ms
Built at: 2019-10-21 10:54:37
Asset Size Chunks Chunk Names
bundle.js 136 KiB 0 [emitted] main
Entrypoint main = bundle.js
[5] ./src/index.scss 498 bytes {0} [built]
[7] ./src/index.js 1.57 KiB {0} [built]
[8] (webpack)/buildin/harmony-module.js 573 bytes {0} [built]
[13] ./src/style/global.css 457 bytes {0} [built]
[14] ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/src!./src/style/global.css 237 bytes {0} [built]
[15] ./node_modules/css-loader/dist/cjs.js!./src/style/reset.css 1.28 KiB {0} [built]
[16] ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src!./node_modules/sass-loader/dist/cjs.js!./src/index.scss 238 bytes {0} [built]
+ 11 hidden modules
✨ Done in 5.90s.
複製代碼
咱們看到這隻打出一個
bundle.js
這顯然作的還不夠。接下來,咱們作幾點改變!
到目前爲止,咱們都是在 index.html 文件中手動引入全部資源,然而隨着應用程序增加,而且一旦開始 在文件名中使用 hash] 並輸出 多個 bundle,若是繼續手動管理 index.html 文件,就會變得困難起來。
修改 webpack - output
const path = require('path');
const webpack = require('webpack');
const IS_PROD = process.env.NODE_ENV === 'production';
...
output: {
path: path.resolve(__dirname, 'dist'),
- publicPath: '/',
+ publicPath: IS_PROD ? '/starter/' : '/', // 公共路徑
- filename: 'bundle.js'
+ filename: IS_PROD ? '[name].[contenthash:8].js' : '[name].js', // 輸出文件的文件名
+ chunkFilename: IS_PROD ? 'chunks/[name].[contenthash:8].js' : '[name].js', // 非入口(non-entry) chunk 文件的名稱
},
...
複製代碼
HtmlWebpackPlugin
$ yarn add -D html-webpack-plugin # 安裝插件
複製代碼
<!-- starter/webpack.config.js -->
const path = require('path');
const webpack = require('webpack');
const IS_PROD = process.env.NODE_ENV === 'production';
+ const HtmlWebpackPlugin = require('html-webpack-plugin')
...
- plugins: []
+ plugins: [
+ new HtmlWebpackPlugin({
+ title: 'Starter',
+ filename: 'index.html',
+ template: path.resolve(__dirname, 'public/index.html'),
+ minify: IS_PROD
+ ? {
+ removeComments: true,
+ collapseWhitespace: true,
+ removeAttributeQuotes: true,
+ collapseBooleanAttributes: true,
+ removeScriptTypeAttributes: true
+ }
+ : {}
+ }),
+ ]
...
複製代碼
修改 public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="./favicon.ico" />
<meta
name="viewport"
content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="This is a react application built from scratch with JavaScript, away from the cli tool."
/>
- <title>Starter</title>
+ <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
- <script src="bundle.js"></script>
</body>
</html>
複製代碼
經過上述配置,讓咱們來看看效果吧
$ yarn build
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: 6bb93a13b6a8a7926f58
Version: webpack 4.41.2
Time: 4418ms
Built at: 2019-10-21 11:47:05
Asset Size Chunks Chunk Names
index.html 553 bytes [emitted]
main.2f781ad1.js 136 KiB 0 [emitted] [immutable] main
Entrypoint main = main.2f781ad1.js
[5] ./src/index.scss 498 bytes {0} [built]
[7] ./src/index.js 1.57 KiB {0} [built]
[8] (webpack)/buildin/harmony-module.js 573 bytes {0} [built]
[13] ./src/style/global.css 457 bytes {0} [built]
[14] ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/src!./src/style/global.css 237 bytes {0} [built]
[15] ./node_modules/css-loader/dist/cjs.js!./src/style/reset.css 1.28 KiB {0} [built]
[16] ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src!./node_modules/sass-loader/dist/cjs.js!./src/index.scss 238 bytes {0} [built]
+ 11 hidden modules
Child html-webpack-plugin for "index.html":
1 asset
Entrypoint undefined = index.html
[0] ./node_modules/html-webpack-plugin/lib/loader.js!./public/index.html 858 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {0} [built]
[3] (webpack)/buildin/module.js 497 bytes {0} [built]
+ 1 hidden module
✨ Done in 6.04s.
複製代碼
<!-- starter/dist/index.html -->
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><link rel=icon href=./favicon.ico><meta name=viewport content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><meta name=theme-color content=#000000><meta name=description content="This is a react application built from scratch with JavaScript, away from the cli tool."><title>React App TS</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id=root></div><script src=/starter/main.2f781ad1.js></script></body></html>
複製代碼
注:若是你仔細看了咱們的輸出,你會發現
main.2f781ad1.js size=136KiB
, 而咱們的代碼卻量卻不多,若是你打開該文件你會發現它包含了react.production.min.js
babel
所需的幫助函數等。
mini-css-extract-plugin - 分離 css 代碼
webpack 默認把 css 和 js 打到一個文件,該插件將CSS提取到單獨的文件中。它爲每一個包含CSS的JS文件建立一個CSS文件。
$ yarn add -D mini-css-extract-plugin # 安裝
複製代碼
<!-- starter/webpack.config.js -->
...
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.(sa|sc)ss$/,
exclude: /node_modules/,
use: [
{
- loader: 'style-loader'
+ loader: IS_PROD ? MiniCssExtractPlugin.loader : 'style-loader',
+ options: IS_PROD ? { publicPath: '../' } : {}
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: {
context: path.resolve(__dirname, 'src'),
localIdentName: '[name]__[local]-[hash:base64:5]'
}
}
},
{
loader: 'postcss-loader'
},
{
loader: 'sass-loader'
}
]
},
{
test: /\.css$/,
exclude: /node_modules/,
- use: ['style-loader', 'css-loader', 'postcss-loader']
+ use: [
+ {
+ loader: IS_PROD ? MiniCssExtractPlugin.loader : 'style-loader',
+ options: IS_PROD ? { publicPath: '../' } : {}
+ },
+ 'css-loader',
+ 'postcss-loader'
+ ]
}
]
},
+ plugins: [
...,
+ new MiniCssExtractPlugin({
+ filename: IS_PROD ? 'css/[name].[contenthash:8].css' : 'css/[name].css',
+ chunkFilename: IS_PROD ? 'css/[name].[contenthash:8].css' : 'css/[name].css'
+ })
]
複製代碼
$ yarn build
$ cross-env NODE_ENV=production webpack --color --progress
Hash: 95fccf0e0844c2df588f
Version: webpack 4.41.2
Time: 4416ms
Built at: 2019-10-21 13:56:23
Asset Size Chunks Chunk Names
! css/main.f9cee851.css 1.08 KiB 0 [emitted] [immutable] main
index.html 605 bytes [emitted]
! main.ced0f821.js 131 KiB 0 [emitted] [immutable] main
Entrypoint main = css/main.f9cee851.css main.ced0f821.js
複製代碼
屢次打包以後咱們發現多處不少上次結果文件,這顯然不能忍受 w(゚Д゚)w; 咱們但願在每次構建以前刪除以前構建生成的文件夾。
clean-webpack-plugin 保持目錄清潔
用於在構建以前刪除您的構建文件夾
$ yarn add -D clean-webpack-plugin # 安裝
複製代碼
<!-- starter/webpack.config.js -->
...
+ const { CleanWebpackPlugin } = require('clean-webpack-plugin');
...
plugins: [
...,
+ new CleanWebpackPlugin()
]
...
複製代碼
試試看👀,清理乾淨了 (。・∀・)ノ゙ Try it!
optimization.splitChunks 將公共的依賴模塊提取到已有的 entry chunk 中
<!-- starter/webpack.config.js -->
...
module.exports = function () {
const baseConfig = {
...
}
+ if (IS_PROD) {
+ baseConfig.optimization = {
+ minimizer: [
+ // Automatically split vendor and commons
+ splitChunks: {
+ chunks: 'all'
+ }
+ ]
+ }
+ }
return baseConfig;
}
複製代碼
$ yarn build # 打包查看效果
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: ebe27d1c4dc54ff22c4b
Version: webpack 4.41.2
Time: 4470ms
Built at: 2019-10-21 14:28:59
Asset Size Chunks Chunk Names
! chunks/vendors~main.f501917c.js 129 KiB 1 [emitted] [immutable] vendors~main
css/main.f9cee851.css 1.08 KiB 0 [emitted] [immutable] main
! index.html 667 bytes [emitted]
! main.76c9ecec.js 2.54 KiB 0 [emitted] [immutable] main
Entrypoint main = chunks/vendors~main.f501917c.js css/main.f9cee851.css main.76c9ecec.js
# 注意:若是你仔細看 chunks/vendors~main.f501917c.js 你會發現 與 react 相關的庫
#(react.production.min.js、react-dom.production.min.js、scheduler.production.min.js)和你代
# 碼所引用的公共庫都將被提取出來,防止重複引用。
複製代碼
webpack 4: Code Splitting, chunk graph and the splitChunks optimization
@babel/plugin-transform-runtime 一個插件,可從新使用Babel注入的幫助程序代碼以節省代碼大小。
$ yarn add -D @babel/plugin-transform-runtime
複製代碼
<!-- starter/postcss.config.js -->
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
+ "@babel/plugin-transform-runtime",
"react-hot-loader/babel"
]
}
複製代碼
$ yarn build
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: 6425898f896ed7244e2b
Version: webpack 4.41.2
Time: 4510ms
Built at: 2019-10-21 15:18:47
Asset Size Chunks Chunk Names
! chunks/vendors~main.e9e35553.js 130 KiB 1 [emitted] [immutable] vendors~main
css/main.f9cee851.css 1.08 KiB 0 [emitted] [immutable] main
index.html 667 bytes [emitted]
! main.5fb316df.js 2.07 KiB 0 [emitted] [immutable] main
Entrypoint main = chunks/vendors~main.e9e35553.js css/main.f9cee851.css main.5fb316df.js
# 能夠比對上次構建結果,主文件減小了一些。
複製代碼
webpack.DefinePlugin 容許建立一個在編譯時能夠配置的全局常量
插件可配置一些全局變量,在構建時將會對代碼內引用的這些變量進行替換。好比:NODE_ENV(經常使用於處理生產環境與開發環境)。若是在開發構建中,而不在發佈構建中執行日誌記錄,則可使用全局常量來決定是否記錄日誌。這就是 DefinePlugin 的用處,設置它,就能夠忘記開發環境和生產環境構建的規則。
<!-- starter/webpack.config.js -->
...
plugins: [
...,
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify(process.env.NODE_ENV),
+ }
+ })
]
...
複製代碼
這裏若是你的代碼沒有對區分環境,作特定處理(去除開發環境下的代碼)則,包尺寸不變。
$ yarn add -D uglifyjs-webpack-plugin
複製代碼
<!-- starter/webpack.config.js -->
...
+ const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
...
if (IS_PROD) {
baseConfig.optimization = {
+ minimizer: [
+ new UglifyjsWebpackPlugin({
+ exclude: /node_modules/,
+ sourceMap: false, // 使用源映射將錯誤消息位置映射到模塊(這會減慢編譯速度)。若是您使用本身的縮小功能,請閱讀縮小部分以正確處理源地圖。
+ cache: true, // 啓用文件緩存
+ parallel: true // 使用多進程並行運行可提升構建速度。併發運行的默認數量:os.cpus().length - 1.
+ })
+ ],
splitChunks: {
chunks: 'all',
}
};
}
複製代碼
$ yarn build # 打包驗證 ✅
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: 3f450244bccc719560c5
Version: webpack 4.41.2
Time: 2209ms
Built at: 2019-10-21 16:25:18
Asset Size Chunks Chunk Names
! chunks/vendors~main.1a122e64.js 129 KiB 1 [emitted] [immutable] vendors~main
css/main.f9cee851.css 1.08 KiB 0 [emitted] [immutable] main
index.html 667 bytes [emitted]
main.e82008bc.js 2.07 KiB 0 [emitted] [immutable] main
Entrypoint main = chunks/vendors~main.1a122e64.js css/main.f9cee851.css main.e82008bc.js
複製代碼
注意:
uglifyjs-webpack-plugin v2.x
版本基於uglify-js
,沒法支持ES6
的壓縮
咱們用
terser-webpack-plugin
替換uglifyjs-webpack-plugin
$ yarn add -D terser-webpack-plugin
複製代碼
<!-- starter/webpack.config.js -->
- const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
+ const TerserPlugin = require('terser-webpack-plugin');
if (IS_PROD) {
baseConfig.optimization = {
minimizer: [
- new UglifyjsWebpackPlugin({
- exclude: /node_modules/,
- sourceMap: false,
- cache: true,
- parallel: true
- }),
+ new TerserPlugin({
+ // Terser minify options.
+ terserOptions: {
+ parse: {
+ // We want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minification steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ // display warnings when dropping unreachable code or unused declarations etc.
+ warnings: false,
+ // apply certain optimizations to binary nodes
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // Pending further investigation: https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ // inline calls to function with simple/return statement:
+ // Disabled because of an issue with Terser breaking valid code:
+ // Pending further investigation: https://github.com/terser-js/terser/issues/120
+ inline: 2, // inline functions with arguments
+ },
+ mangle: {
+ // Pass true to work around the Safari 10 loop iterator bug "Cannot declare a let variable twice".
+ // See also: the safari10 output option.
+ safari10: true,
+ },
+ // Added for profiling in devtools
+ keep_classnames: true,
+ keep_fnames: true,
+ output: {
+ ecma: 5,
+ // pass true or "all" to preserve all comments, "some" to preserve some comments,
+ // a regular expression string (e.g. /^!/) or a function.
+ comments: false,
+ // escape Unicode characters in strings and regexps (affects directives with non-ascii characters becoming invalid)
+ // Turned on because emoji and regex is not minified properly using default
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed.
+ //Default number of concurrent runs: os.cpus().length - 1.
+ parallel: true,
+ cache: true, // Enable file caching
+ }),
],
splitChunks: {
chunks: 'all',
}
};
}
複製代碼
$ yarn build
$ cross-env NODE_ENV=production webpack --color --progress
Hash: dbf5243d5591e4ac0268
Version: webpack 4.41.2
Time: 2461ms
Built at: 2019-10-21 17:52:26
Asset Size Chunks Chunk Names
! chunks/vendors~main.ae62441b.js 130 KiB 1 [emitted] [immutable] vendors~main
! chunks/vendors~main.ae62441b.js.LICENSE 790 bytes [emitted]
css/main.f9cee851.css 1.08 KiB 0 [emitted] [immutable] main
index.html 667 bytes [emitted]
! main.2130b172.js 2.52 KiB 0 [emitted] [immutable] main
Entrypoint main = chunks/vendors~main.ae62441b.js css/main.f9cee851.css main.2130b172.js
複製代碼
optimize-css-assets-webpack-plugin - 優化/減小CSS資產
$ yarn add -D optimize-css-assets-webpack-plugin # 壓縮 CSS
$ yarn add -D postcss-safe-parser # 查找並修復 CSS 語法錯誤
複製代碼
<!-- starter/webpack.config.js -->
...
+ const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
+ const SafePostCssParser = require('postcss-safe-parser');
if (IS_PROD) {
baseConfig.optimization = {
minimizer: [
...
+ new OptimizeCSSAssetsPlugin({
+ // The options passed to the cssProcessor, defaults to {}
+ // cssProcessor: The CSS processor used to optimize \ minimize the CSS, defaults to cssnano.
+ // This should be a function that follows cssnano.process interface
+ // (receives a CSS and options parameters and returns a Promise).
+ cssProcessorOptions: {
+ parser: SafePostCssParser,
+ map: false,
+ },
+ })
],
...
};
}
...
複製代碼
$ yarn build # 打包實驗 ✅
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: dbf5243d5591e4ac0268
Version: webpack 4.41.2
Time: 3543ms
Built at: 2019-10-21 20:17:23
Asset Size Chunks Chunk Names
chunks/vendors~main.ae62441b.js 130 KiB 1 [emitted] [immutable] vendors~main
chunks/vendors~main.ae62441b.js.LICENSE 790 bytes [emitted]
! css/main.f9cee851.css 869 bytes 0 [emitted] [immutable] main
index.html 667 bytes [emitted]
main.2130b172.js 2.52 KiB 0 [emitted] [immutable] main
Entrypoint main = chunks/vendors~main.ae62441b.js css/main.f9cee851.css main.2130b172.js
複製代碼
從輸出的 bundle 中排除依賴; 防止將某些 import 的包打包到 bundle 中,而是在運行時(runtime)再去從外部獲取這些擴展依賴(external dependencies)。
CDN - 此步可忽略
<!-- starter/webpack.config.js -->
...
module.exports = function() {
const baseConfig = {
...
resolve: {
alias: {
'react-dom': '@hot-loader/react-dom' // react-hot-loader 兼容 hook 寫法
}
},
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM'
+ },
...
}
...
<!-- starter/public/index.html -->
...
<div id="root"></div>
+ <script crossorigin src="https://unpkg.com/react@16.10.2/umd/react.production.min.js"></script>
+ <script crossorigin src="https://unpkg.com/@hot-loader/react-dom@16.10.2/umd/react-dom.production.min.js"></script>
...
複製代碼
$ yarn build
$ cross-env NODE_ENV=production webpack --color --progress
Hash: 6bb2de2632bdaf2dc081
Version: webpack 4.41.2
Time: 2557ms
Built at: 2019-10-21 21:35:38
Asset Size Chunks Chunk Names
css/main.34dd0d40.css 869 bytes 0 [emitted] [immutable] main
index.html 811 bytes [emitted]
main.da7fbe78.js 3.85 KiB 0 [emitted] [immutable] main
Entrypoint main = css/main.34dd0d40.css main.da7fbe78.js
複製代碼
1. CDN是什麼?使用CDN有什麼優點?
2. 幾個 CDN 公共庫:cdnjs、jsdelivr、unpkg
3. 爲提升訪問速度,最好把前端不常更新的類庫,如,react、react-dom、axios、moment等從輸出的 bundle 中排除依賴
4. 提示,最好本身弄個,用本身的老是來得保險一些 🤡
└── starter
+ ├── dist
+ │ └── chunks
+ │ │ ├── vendors~main.ae62441b.js
+ │ │ └── vendors~main.ae62441b.js.LICENSE
+ ├── css
+ │ │ └── main.f9cee851.css
+ │ ├── index.html
+ │ └── main.2130b172.js
├── node_modules
├── public
│ ├── favicon.ico
│ └── index.html
├── src
+ │ └── style
+ │ | ├── global.css
+ │ | └── reset.css
| ├── index.js
+ │ ├── index.scss
+ ├── postcss.config.js
├── webpack.config.js
├── package.json
├── README.md
├── LICENSE
└── yarn.lock
複製代碼
前端單頁應用,路由必不可少,目前主流框架都有配套路由插件,這裏配合所選框架引入 react-router-dom
安裝
$ yarn add react-router-dom
複製代碼
新建路由配置文件夾
$ cd src && mkdir router # 新建 router 文件夾
$ cd router
$ touch index.js # 新建路由配置文件
$ touch list.js # 新建路由表文件
複製代碼
編寫路由配置及路由表
路由配置 - src/router/index.js
import React from 'react';
import {
BrowserRouter,
Route,
Switch,
Redirect
} from 'react-router-dom';
import routes from './list';
function RouterView(route) {
return (
<Route
path={route.path}
render={(props) => {
if (route.redirect) {
return <Redirect to={route.redirect} />;
}
return (
<route.component
{...props}
render={() => (
<Switch>
{route.routes.map((children) => (
<RouterView key={children.path} {...children} />
))}
</Switch>
)}
/>
);
}}
/>
);
}
export default function Router() {
return (
<BrowserRouter>
<Switch>
{routes.map((route) => (
<RouterView key={route.path} {...route} />
))}
</Switch>
</BrowserRouter>
);
}
複製代碼
這裏根據路由配置文檔編寫的,僅作 DEMO 使用;詳情參閱 react-router: Route Config
路由表 - src/router/list.js
import Github from '../views/Github/Github';
import Setting from '../views/Setting/Setting';
const routes = [
{
path: '/',
exact: true,
redirect: '/github'
},
{
path: '/github',
component: Github,
},
{
path: '/setting',
component: Setting,
}
];
export default routes;
複製代碼
這裏的命名你能夠隨意建立🙄
新建 Setting、GitHub 頁面,並編寫
# 新建 Setting、GitHub 頁面
$ cd src/views
$ mkdir Github && cd Github
$ touch Github.js && touch Github.scss
$ cd ..
$ mkdir Setting && cd Setting
$ touch Setting.js && touch Setting.scss
$ cd ..
複製代碼
// starter/Github/Github.js
import React from 'react';
import { useHistory } from 'react-router-dom';
import styles from './Github.scss';
function Github() {
const history = useHistory();
function handleClick() {
history.push('/setting');
}
return (
<div className={`${styles.root}`}> <h1>Github</h1> <div className={`${styles.bg} ${styles.wh}`}> {`當前環境: ${process.env.NODE_ENV}`} </div> <button type='button' onClick={handleClick}> Go setting </button> </div>
);
}
export default Github;
// starter/Setting/Setting.js
import React from 'react';
import { useHistory } from 'react-router-dom';
import styles from './Setting.scss';
function Setting() {
const history = useHistory();
function handleClick() {
history.push('/github');
}
return (
<div className={`${styles.root}`}> <h1>Setting</h1> <div className={`${styles.bg} ${styles.wh}`}> {`當前環境: ${process.env.NODE_ENV}`} </div> <button type='button' onClick={handleClick}> Go github </button> </div>
);
}
export default Setting;
複製代碼
// starter/Setting/Setting.scss
.root {
.wh {
width: 200px;
height: 180px;
}
.bg {
text-align: center;
line-height: 180px;
background: no-repeat url('~assets/images/logo.png');
}
}
// starter/Github/Github.scss
.root {
.wh {
width: 200px;
height: 200px;
}
.bg {
text-align: center;
line-height: 200px;
background: no-repeat url('~assets/images/logo.png');
}
}
複製代碼
因爲樣式引入圖片,因此咱們新建資源存放文件夾,用來存放這些資源
$ cd src && mkdir assets
$ cd assets && mkdir images
$ cd images
$ copy logo.png # 這裏的圖標是官網摟過來的,🤣
複製代碼
修改咱們的主文件 src/index.js
import { hot } from 'react-hot-loader';
- import React, { useState } from 'react';
+ import React from 'react';
import ReactDom from 'react-dom';
import './style/global.css';
- import styles from './index.scss';
+ import Router from './router/index';
- const App = hot(module)(() => {
- const reversedTitle = () =>
- setTitle(
- title
- .split('')
- .reverse()
- .join('')
- );
- return (
- <div className={styles.app}>
- <h1>{title}</h1>
- <button type='button' onClick={reversedTitle}>
- reversed title!
- </button>
- </div>
- );
- });
+ const App = hot(module)(() => (
+ <div className='app'>
+ <Router />
+ </div>
+ ));
ReactDom.render(<App />, document.getElementById('root'));
複製代碼
如今一切準備就緒,但在啓動項目以前,首先說明幾點
webpack
- resolve.alias
, 建立 import 或 require 的別名,來確保模塊引入變得更簡單。作點改進吧️ ⚓️
模塊解析
<!-- starter/webpack.config.js -->
...
resolve: {
alias: {
'react-dom': '@hot-loader/react-dom', // react-hot-loader 兼容 hook 寫法
+ '@': path.resolve(__dirname, 'src'),
+ assets: path.resolve(__dirname, 'src/assets'),
+ style: path.resolve(__dirname, 'src/style')
}
},
...
複製代碼
管理資源
# 安裝
$ yarn add -D url-loader # 將文件轉換爲 base64 URI。
$ yarn add -D file-loader # 將文件上的 import/require() 解析爲 url,並將該文件發射到輸出目錄中。
複製代碼
<!-- starter/webpack.config.js -->
module: {
rules: [
...
+ {
+ test: /\.(png|jpe?g|gif|webp)(\?.*)?$/, // 匹配這些格式的圖片
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 4096, // 文件大小等於或大於限制,則將使用 file-loader。
+ fallback: {
+ loader: 'file-loader',
+ options: {
+ name: 'images/[name].[hash:8].[ext]'
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ test: /\.(svg)(\?.*)?$/,
+ use: [
+ {
+ loader: 'file-loader',
+ options: {
+ name: 'svg/[name].[hash:8].[ext]'
+ }
+ }
+ ]
+ },
+ {
+ test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 4096,
+ fallback: {
+ loader: 'file-loader',
+ options: {
+ name: 'fonts/[name].[hash:8].[ext]'
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+ use: [
+ {
+ loader: 'url-loader',
+ options: {
+ limit: 4096,
+ fallback: {
+ loader: 'file-loader',
+ options: {
+ name: 'media/[name].[hash:8].[ext]'
+ }
+ }
+ }
+ }
+ ]
+ }
+ ]
}
複製代碼
這裏咱們雖然沒有引入
svg
、字體圖標文件、音頻文件,可是這裏爲了方便後續深刻,咱們索性把其配置添加。
好了,啓動咱們的項目。Try it!
$ yarn server
複製代碼
打包
$ yarn build
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: fb9c0cc487e7845fd915
Version: webpack 4.41.2
Time: 3533ms
Built at: 2019-10-23 11:42:44
Asset Size Chunks Chunk Names
chunks/vendors~main.64d1203b.js 160 KiB 1 [emitted] [immutable] vendors~main
chunks/vendors~main.64d1203b.js.LICENSE 1.01 KiB [emitted]
! css/main.b7d00a9e.css 1.19 KiB 0 [emitted] [immutable] main
images/logo.581fa1d8.png 8.38 KiB [emitted]
index.html 667 bytes [emitted]
! main.cfa18e59.js 3.95 KiB 0 [emitted] [immutable] main
Entrypoint main = chunks/vendors~main.64d1203b.js css/main.b7d00a9e.css main.cfa18e59.js
複製代碼
問題與待優化點
注:使用該插件對應用進行代碼分割可以幫助你「懶加載」當前用戶所須要的內容,可以顯著地提升你的應用性能。儘管並無減小應用總體的代碼體積,但你能夠避免加載用戶永遠不須要的代碼,並在初始加載的時候減小所需加載的代碼量。
安裝
# 固然你也能夠選擇,React.lazy 和 Suspense,但他們還不支持服務端渲染。這裏直接選擇功能更增強大的 @loadable/component
$ yarn add @loadable/component
複製代碼
修改路由表
! <!-- src/router/list -->
- import Github from '@/views/Github/Github';
- import Setting from '@/views/Setting/Setting';
+ import React from 'react';
+ import loadable from '@loadable/component';
+ const Github = import(/* webpackChunkName: "github" */ '@/views/Github/Github.js');
+ const Setting = import/* webpackChunkName: "setting" */ ('@/views/Setting/Setting.js');
+ const AsyncComponent = (loader) => loadable(loader, { fallback: <h3>Loading...</h3> });
const routes = [
{
path: '/',
exact: true,
redirect: '/github'
},
{
path: '/github',
- component: Github
+ component: AsyncComponent(() => Github)
},
{
path: '/setting',
- component: Setting
+ component: AsyncComponent(() => Setting)
}
];
export default routes;
複製代碼
打包咱們的應用,看一看代碼分割結果
$ yarn build
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: b93be70da668f4dff43b
Version: webpack 4.41.2
Time: 6077ms
Built at: 2019-10-23 16:21:08
Asset Size Chunks Chunk Names
! chunks/github.45dc6c0d.js 634 bytes 0 [emitted] [immutable] github
! chunks/setting.316d765f.js 637 bytes 2 [emitted] [immutable] setting
chunks/vendors~main.a51021eb.js 164 KiB 3 [emitted] [immutable] vendors~main
chunks/vendors~main.a51021eb.js.LICENSE 1.01 KiB [emitted]
! css/github.8de607a6.css 191 bytes 0 [emitted] [immutable] github
! css/setting.1a0bfbdd.css 195 bytes 2 [emitted] [immutable] setting
css/main.c1fb052e.css 830 bytes 1 [emitted] [immutable] main
images/logo.581fa1d8.png 8.38 KiB [emitted]
index.html 667 bytes [emitted]
main.02bdd0e7.js 4.96 KiB 1 [emitted] [immutable] main
Entrypoint main = chunks/vendors~main.a51021eb.js css/main.c1fb052e.css main.02bdd0e7.js
複製代碼
工程目錄
└── starter
+ ├── dist
+ │ └── chunks
+ │ │ ├── github.45dc6c0d.js
+ │ │ ├── setting.316d765f.js
+ │ │ ├── vendors~main.a51021eb.js
+ │ │ └── vendors~main.a51021eb.js.LICENSE
+ ├── css
+ │ │ ├── 0.8de607a6.css
+ │ │ ├── 2.1a0bfbdd.css
+ │ │ └── main.c1fb052e.css
+ │ ├── images
+ │ │ └── logo.581fa1d8.png
│ ├── index.html
+ │ └── main.02bdd0e7.js
├── node_modules
├── public
│ ├── favicon.ico
│ └── index.html
├── src
+ │ ├── assets
+ │ │ └── images
+ │ │ └── logo.png
+ │ ├── router
+ │ │ ├── index.js
+ │ │ └── list.js
│ ├── style
│ | ├── global.css
│ | └── reset.css
+ | ├── views
+ │ | ├── Github
+ │ | │ ├── Github.js
+ │ | │ └── Github.scss
+ │ | └── Setting
+ │ | ├── Setting.js
+ │ | └── Setting.scss
- │ ├── index.scss
| └── index.js
├── postcss.config.js
├── webpack.config.js
├── package.json
├── README.md
├── LICENSE
└── yarn.lock
複製代碼
到此,咱們已經把路由功能添加,繼續後續工做吧!🚘
到目前爲止,咱們項目的代碼量愈來愈多了,寫的代碼可能還會存在一些潛在問題(這很難避免);再一個,一個大型項目每每是一個團隊在維護,團隊成員代碼風格卻不盡相同。基於此,咱們須要一個工具去解決這些痛點。
工具
eslint: 經常使用於檢查常見的 JavaScript 代碼錯誤,也能夠進行代碼風格檢查。
stylelint: 強大的現代化 linter,可幫助您避免錯誤並在樣式中強制執行約定。
prettier: 代碼格式化工具,它經過解析代碼並使用本身的規則從新打印代碼,從而實現一致的樣式,並在必要時包裝代碼。
論述完編碼規範的重要性,及工具鏈以後,咱們看看如何在項目中應用。
安裝
$ yarn add -D eslint # eslint
$ yarn add -D babel-eslint # 一個對 Babel 解析器的包裝,使其可以與 ESLint 兼容
$ yarn add -D eslint-plugin-react # 檢測 react 代碼
$ yarn add -D eslint-plugin-react-hooks # 用於檢測 hook 規則
$ yarn add -D eslint-plugin-jsx-a11y # 用於檢測 jsx 規範
$ yarn add -D eslint-plugin-import # ESLint 插件,帶有有助於驗證正確導入的規則。
$ yarn add -D eslint-import-resolver-webpack # 用於 eslint-plugin-import的 Webpack-literate 模塊解析插件。
複製代碼
新建 eslint 配置文件
$ touch .eslintrc # eslint 配置文件
$ touch .eslintignore # eslint 忽略檢測配置文件
複製代碼
<!-- starter/.eslintrc -->
{
"root": true,
"env": {
"es6": true,
"browser": true,
},
"extends": ["eslint:recommended", "plugin:react/recommended", "plugin:jsx-a11y/recommended"],
"parser": "babel-eslint",
"plugins": ["react", "jsx-a11y", "react-hooks", "import"],
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "single"],
"camelcase": [0, { "properties": "never" }],
"no-console": [2, { "allow": ["warn", "error"] }],
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/jsx-props-no-spreading": "off",
"jsx-a11y/click-events-have-key-events": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/no-unused-prop-types": "off"
},
"settings": {
"react": {
"version": "16.10.2"
},
"import/resolver": "webpack"
},
"globals": {
"process": true,
"module": true
}
}
<!-- starter/.eslintignore -->
node_modules
dist
複製代碼
配置說明
"eslint:recommended"
啓用推薦的規則"plugin:react/recommended"
該插件會導出建議的配置,以強制實施 React 的良好作法。"babel-eslint"
一個對 Babel 解析器的包裝,使其可以與 ESLint 兼容eslint-plugin-import
該插件旨在支持ES2015 +(ES6 +)導入/導出語法的檢查,並防止文件路徑和導入名稱拼寫錯誤的問題。"import/resolver": "webpack"
:解決 webpack 別名配置致使的 eslint-plugin-import
報錯。注:eslint 配置須要根據團隊內部去協定出一套行之有效的規範。
修改 package.json 新建快捷命令
<!-- starter/package.json -->
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
"build": "cross-env NODE_ENV=production webpack --color --progress",
+ "lint:script": "eslint --ext '.js,.jsx' src",
+ "lint-fix:script": "npm run lint:script -- --fix"
},
複製代碼
執行命令,查看是否存在不符合規則之處
$ yarn lint:script # 執行 lint
$ yarn lint-fix:script # 執行 lint 並自動修復
# 結果, 若是存在錯誤,則根據文檔自行修復。
$ npm run lint:script -- --fix
> starter@1.0.0 lint:script /Users/gt/LEMON/starter
> eslint --ext '.js,.jsx' src "--fix"
✨ Done in 2.59s.
複製代碼
另外,咱們但願在每次轉譯js、jsx文件以前,執行 lint 格式化代碼
# 安裝
$ yarn add -D eslint-loader # eslint loader (for webpack)
複製代碼
// 修改 webpack.config.js 配置
...
module: {
rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ include: path.resolve(__dirname, 'src'),
+ enforce: 'pre',
+ use: [
+ {
+ loader: 'eslint-loader',
+ options: {
+ cache: false,
+ fix: true
+ }
+ }
+ ]
+ },
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
...
]
}
...
複製代碼
測試一下吧. Try it 🚨
安裝
$ yarn add -D stylelint # 強大的現代化 linter,可幫助您避免錯誤並在樣式中強制執行約定。
$ yarn add -D stylelint-config-recommended # Stylelint 的推薦可共享配置
$ yarn add -D postcss-reporter # 在控制檯中記錄 PostCSS 消息
# $ yarn add -D stylelint-config-standard # Stylelint 的標準可共享配置
# stylelint 插件經過 PostCSS 註冊警告 。所以,您須要用於打印警告的 PostCSS 運行器或插件,其目的是格式化和打印警告(例如 postcss-reporter)
複製代碼
新建 stylelint 配置文件
$ touch .stylelintrc # stylelint 配置文件
複製代碼
<!-- starter/.eslintrc -->
# 你也可使用 stylint 推薦開啓的規則, 只需引入擴展推薦包便可。
# 你也能夠 使用 rules 擴充規則或者覆蓋推薦規則,這取決於你!
{
"extends": "stylelint-config-recommended",
"rules": {
"indentation": 2, // 縮進
"declaration-colon-space-after": "always", // 在冒號聲明後須要一個空格或禁止使用空格。 a { color:pink } => a { color: pink }
"declaration-colon-space-before": "never", // 在冒號以前須要一個空格或禁止空格。 a { color : pink } => a { color: pink }
"function-comma-space-after": "always", // 在功能的逗號後面須要一個空格或不容許空格。 a { transform: translate(1,1) } => a { transform: translate(1, 1) }
"function-url-quotes": "always", // 要求或禁止使用網址引號 a { background: url(x.jpg) } => a { background: url("x.jpg") }
"media-feature-colon-space-before": "never", // 媒體功能中的冒號以前須要單個空格或不容許使用空格。@media (max-width :600px) {} => @media (max-width:600px) {}
"media-feature-name-no-vendor-prefix": true, // 禁止使用媒體功能名稱的供應商前綴。@media (-webkit-min-device-pixel-ratio: 1) {} => @media (min-resolution: 96dpi) {}
"max-empty-lines": 5, // 限制相鄰的空行數。
"number-leading-zero": "never", // 小數部分小於或等於1的前導零。a { line-height: 0.5; } => a { line-height: .5; }
"number-no-trailing-zeros": true, // 禁止數字尾隨零。a { top: 1.0px } => a { top: 1px }
"at-rule-semicolon-newline-after": "always", // 規則後的分號換行符 @import url("x.css"); a {} => @import url("x.css");\n a {}
"selector-list-comma-space-before": "never", // 選擇器列表的逗號前須要一個空格或不容許空格 a ,b { color: pink; } => a, b { color: pink; }
"selector-list-comma-newline-after": "always", // 選擇器列表的逗號後須要換行符或不容許使用空格。a, b { color: pink; } => a,\n b { color: pink; }
"string-quotes": "single", // 在字符串周圍指定單引號或雙引號。 a { content: 「x」; } => a { content: 'x'; }
}
}
複製代碼
擴展共享配置及規則表
添加快捷命令
<!-- starter/package.json -->
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
"build": "cross-env NODE_ENV=production webpack --color --progress",
"lint:script": "eslint --ext '.js,.jsx' src",
"lint-fix:script": "npm run lint:script -- --fix",
+ "lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
+ "lint-fix:style": " npm run lint:style -- --fix",
},
複製代碼
配置 postcss-reporter
在控制檯中記錄 PostCSS 消息
<!-- starter/postcss.config.js -->
module.exports = {
plugins: {
autoprefixer: {},
+ 'postcss-reporter': {
+ clearReportedMessages: true, # 插件將在記錄結果消息後清除它們。這樣能夠防止其餘插件或您使用的任何運行程序再次記錄相同的信息並引發混亂。
+ throwError: true # 在插件記錄您的消息後,若是發現任何警告,它將引起錯誤。
+ },
}
};
複製代碼
執行命令,查看是否存在不符合規則之處
$ yarn lint:style # 格式化 style
$ yarn lint-fix:style # 格式化 style 並自動修復
# 結果
$ npm run lint:style -- --fix
> starter@1.0.0 lint:style /Users/gt/LEMON/starter
> stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss "--fix"
src/style/reset.css
54:1 ✖ Expected selector "h1" to come before selector "h1:first-child" no-descending-specificity
54:1 ✖ Expected selector "h1" to come before selector "h1:last-child" no-descending-specificity
58:1 ✖ Expected selector "h2" to come before selector "h2:first-child" no-descending-specificity
58:1 ✖ Expected selector "h2" to come before selector "h2:last-child" no-descending-specificity
62:1 ✖ Expected selector "h3" to come before selector "h3:first-child" no-descending-specificity
62:1 ✖ Expected selector "h3" to come before selector "h3:last-child" no-descending-specificity
66:1 ✖ Expected selector "h4" to come before selector "h4:first-child" no-descending-specificity
66:1 ✖ Expected selector "h4" to come before selector "h4:last-child" no-descending-specificity
67:1 ✖ Expected selector "h5" to come before selector "h5:first-child" no-descending-specificity
67:1 ✖ Expected selector "h5" to come before selector "h5:last-child" no-descending-specificity
68:1 ✖ Expected selector "h6" to come before selector "h6:first-child" no-descending-specificity
68:1 ✖ Expected selector "h6" to come before selector "h6:last-child" no-descending-specificity
# no-descending-specificity 禁止較低特異性的選擇器在覆蓋較高特異性的選擇器以後出現。
# 根據規則表修復 reset.css 文件
# 再次運行,結果:
$ npm run lint:style -- --fix
> starter@1.0.0 lint:style /Users/gt/LEMON/starter
> stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss "--fix"
✨ Done in 1.96s.
複製代碼
測試一下吧. Try it 💄
安裝
$ yarn add -D prettier
$ yarn add -D eslint-plugin-prettier # 將 Prettier 做爲 ESLint 規則運行,並將差別報告爲單個ESLint問題
$ yarn add -D eslint-config-prettier # 關閉全部沒必要要的或可能與 Prettier 衝突的規則。
$ yarn add -D stylelint-config-prettier # 禁用與 Prettier 衝突的規則的配置
複製代碼
關於這些禁用規則,請參考 eslint-config-prettier#special-rules, stylelint-config-prettier special-rules
在 eslint 配置中擴展 prettier
<!-- starter/.eslintrc -->
{
...
- "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:jsx-a11y/recommended"],
+ "extends": [
+ "eslint:recommended",
+ "plugin:react/recommended",
+ "plugin:jsx-a11y/recommended",
+ "plugin:prettier/recommended",
+ "prettier/react"
+ ],
...
}
<!-- 說明 -->
"plugin:prettier/recommended" does three things:
1. Enables eslint-plugin-prettier.
2. Sets the prettier/prettier rule to "error".
3. Extends the eslint-config-prettier configuration.
"prettier/react"
爲了支持特殊的 ESLint 插件(eslint-plugin-react)所添加額外的排除項
複製代碼
固然,你能夠在
.prettierrc
文件中設置Prettier
本身的選項。
新建 prettier 配置文件
$ touch .prettierrc # prettier 配置文件
複製代碼
<!-- starter/.prettierrc -->
{
"semi": true,
"singleQuote": true,
"trailingComma": 'all',
}
複製代碼
在 stylelint 配置中擴展 prettier
<!-- starter/.stylelintrc -->
{
...
- "extends": "stylelint-config-recommended",
+ "extends": [
+ "stylelint-config-recommended",
+ "stylelint-config-prettier"
+ ],
...
}
複製代碼
說明
添加快捷命令行
<!-- starter/package.json -->
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
"build": "cross-env NODE_ENV=production webpack --color --progress",
"lint:script": "eslint --ext '.js,.jsx' src",
"lint-fix:script": "npm run lint:script -- --fix",
"lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
"lint-fix:style": " npm run lint:style -- --fix",
+ "prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
},
...
複製代碼
更多參數請參考 Prettier CLI
運行命令,格式化代碼
$ yarn prettier
# 結果, 它幫你格式化的代碼以下
$ prettier --check --write './src/**/*.js' './src/**/*.jsx'
Checking formatting...
src/index.js
src/router/index.js
src/router/list.js
src/views/Github/Github.js
src/views/Setting/Setting.js
Code style issues fixed in the above file(s).
✨ Done in 0.79s.
複製代碼
Git鉤子腳本對於在提交代碼審查以前識別簡單問題頗有用。咱們在每次提交時都運行鉤子,以自動指出代碼中的問題,例如缺乏分號,尾隨空白和調試語句。經過在代碼審閱以前指出這些問題,一來,能夠確保沒有錯誤進入存儲庫;二來,代碼審閱者能夠專一於更改的體系結構,而不會由於瑣碎的風格問題而浪費時間。
安裝
$ yarn add -D husky # 🐶 Git hooks made easy
$ yarn add -D lint-staged # 對暫存的 git 文件運行 linters,不要讓💩進入您的代碼庫!
複製代碼
配置
<!-- starter/package.json -->
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
"build": "cross-env NODE_ENV=production webpack --color --progress",
+ "lint": "npm run lint:style && npm run lint:script",
+ "lint-fix": "npm run lint-fix:style && npm run lint-fix:script",
"lint:script": "eslint --ext '.js,.jsx' src",
"lint-fix:script": "npm run lint:script -- --fix",
"lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
"lint-fix:style": " npm run lint:style -- --fix",
"prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
},
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ "src/**/*.{js, jsx, css, scss}": [
+ "npm run prettier",
+ "npm run lint-fix",
+ "git add"
+ ]
+ }
}
複製代碼
推個代碼測試一下吧! Try it! 🎊🎊
題外話:commit changelog 規範
# feat: 添加新功能(feature)
# fix : 修復 bug
# docs: 文檔(documentation)
# style: 樣式及代碼格式化等不涉及邏輯的改動點
# refactor: 重構
# test: 添加測試用例
# chore: 構建過程或輔助工具的變更
# 這裏推薦一個 lint 插件 commitlint。可根據須要添加
# 詳細參考:https://github.com/conventional-changelog/commitlint
# 關於 commit 信息編寫的更多規範指南
# 請參考:http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html
複製代碼
到此編碼規範的內容基本陳述完畢,說的東西有限、具體如何配置取決於你或你的團隊要求! Go 🚠
爲了接下來更好的論述,咱們來完成一個小需求。
由草圖需求改造咱們的項目
改造路由表
import React from 'react';
import loadable from '@loadable/component';
import Loading from '@/components/Loading/Loading';
const BottomTabNavigator = import(
/* webpackChunkName: "bottom-tab-navigator" */ '@/components/BottomTabNavigator/BottomTabNavigator'
);
const Empty = import(
/* webpackChunkName: 'not-found' */ '@/components/Empty/Empty'
);
const Github = import(/* webpackChunkName: "github" */ '@/views/Github/Github');
const Setting = import(
/* webpackChunkName: "setting" */ '@/views/Setting/Setting'
);
const AsyncComponent = loader => loadable(loader, { fallback: <Loading /> }); const routes = [ { path: '/', exact: true, redirect: '/dashboard/github', }, { path: '/dashboard', component: AsyncComponent(() => BottomTabNavigator), routes: [ { path: '/dashboard/github', component: AsyncComponent(() => Github), }, { path: '/dashboard/setting', component: AsyncComponent(() => Setting), }, ], }, { path: '*', component: AsyncComponent(() => Empty), }, ]; export default routes; 複製代碼
改造 Github 頁面
/* * 路徑: starter/src/views/Github * 說明: * RepositoriesCard 根據草圖編寫的倉庫信息卡片 * Loading 加載態組件 * Empty 空數據態組件 * useRequest 自定義 hook,用於包裝請求 * searchRepositories 統一 API 請求封裝 * * 提示: 說明涉及到的組件,能夠參考項目;你也能夠本身實現,這不重要。 */
import React from 'react';
import styles from './Github.scss';
import RepositoriesCard from '@/components/RepositoriesCard/RepositoriesCard';
import Loading from '@components/Loading/Loading';
import Empty from '@components/Empty/Empty';
import useRequest from '@/containers/useRequest';
import { searchRepositories } from '@/services/api/github';
function Github() {
const [loading, data] = useRequest(searchRepositories, { q: 'javascript' });
if (loading === true) {
return <Loading />
}
return (
<div className={styles.root}>
{(data && data.items.map(
({
description,
id,
name,
forks_count,
stargazers_count,
language,
owner
}) => (
<RepositoriesCard
key={id}
name={name}
avatarUrl={owner.avatar_url}
description={description}
stargazersCount={stargazers_count}
forksCount={forks_count}
language={language}
/>
)
))
|| <Empty />}
</div>
);
}
export default Github;
複製代碼
改造 Setting 頁面(不改造 😜)
咱們在改造 Github 頁面, 在組件內部調用了請求方法,並對請求作了統一封裝,在繼續改造工做以前,咱們先來看看 先後端交互
安裝
$ yarn add axios # Promise based HTTP client for the browser and node.js
複製代碼
新建相關文件
# 新建 services 文件夾
$ cd src && mkdir services
$ cd services && touch index.js # 基於 axios 簡單封裝
$ mkdir interface && cd interface # 用於存在項目全部接口
$ touch github.js # 用於存放 GitHub 相關請求
複製代碼
基於 axios 簡單封裝 src/services/index.js
/** * 說明: AXIOS_DEFAULT_OPTIONS 默認配置,詳細參考 utils * * 注: 如下封裝僅僅簡單包裝一層,你也能夠本身實現。 */
import axios from 'axios';
import constants from '@/utils/constants';
// 使用自定義配置新建一個 axios 實例
const instance = axios.create(constants.AXIOS_DEFAULT_OPTIONS);
// 請求攔截器
instance.interceptors.request.use(
(AxiosRequsetConfig) => AxiosRequsetConfig, // 在發送請求以前作些什麼
(error) => Promise.reject(error) // 對請求錯誤作些什麼
);
// 響應攔截器
instance.interceptors.response.use(
(AxiosResponse) => AxiosResponse, // 對響應數據作點什麼
(error) => Promise.reject(error) // 對響應錯誤作點什麼, 如,處理一些鑑權類問題
);
export default function (options = {}, customConfig = {}) {
return new Promise((resolve, reject) => {
const finalConfig = Object.assign(options, customConfig);
instance(finalConfig)
.then(({ data }) => {
if (data) {
return resolve(data);
}
return reject(new Error('Request return result exception!'));
})
.catch((reason) => reject(reason));
});
}
複製代碼
業務接口層 src/services/interface/github.js
import network from '../index';
/** * @desc 搜索倉庫 * * @param {Object} data 請求參數 * @returns {Promise} */
export const searchRepositories = (data = {}) => network({
url: '/search/repositories',
params: data
});
複製代碼
上述簡單封裝核心請求方法,分離接口等,主要目的是輔助論述,固然,這還很簡單,你能夠本身根據實際須要作更全面的封裝!
準則
樣例
import React from 'react';
import PropTypes from 'prop-types';
const UI = ({ title }) => {
return (
<div className="UI"> { title } </div>
);
};
UI.propTypes = {
title: PropTypes.string,
};
UI.defaultProps = {
title: 'UI Component !',
};
export default UI;
複製代碼
準則
樣例
import { connect } from 'react-redux';
import Demo from 'components/Demo/Demo';
import {
incrementEnthusiasm,
decrementEnthusiasm
} from 'actions/index';
export function mapStateToProps({ enthusiasm }) {
return {
enthusiasm,
};
}
export function mapDispatchToProps(dispatch) {
return {
onIncrement: () => dispatch(actions.incrementEnthusiasm()),
onDecrement: () => dispatch(actions.decrementEnthusiasm()),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Demo);
複製代碼
Tip: 因爲對 react 不是很熟,故談的比較簡單,這裏推薦參考:Presentational and Container Components、編寫有彈性的組件
這裏咱們直接引入
postcss-px-to-viewport
插件。
安裝
$ yarn add -D postcss-px-to-viewport`
複製代碼
配置
<!-- starter/postcss-config.js -->
module.exports = {
plugins: {
autoprefixer: {}
},
'postcss-reporter': {
clearReportedMessages: true,
throwError: true
},
+ 'postcss-px-to-viewport': {
+ viewportWidth: 375, // 設計稿的視口寬度
+ viewportHeight: 812, // 設計稿的視口高度
+ unitPrecision: 5, // 單位轉換後保留的精度
+ viewportUnit: 'vw', // 但願使用的視口單位
+ fontViewportUnit: 'vw', // 字體使用的視口單位
+ selectorBlackList: ['.ignore', '.hairlines'], // 須要忽略的CSS選擇器,不會轉爲視口單位,使用原有的px等單位。
+ minPixelValue: 1, // 設置最小的轉換數值,若是爲1的話,只有大於1的值會被轉換
+ mediaQuery: false, // 媒體查詢裏的單位是否須要轉換單位
+ exclude: [/node_modules/] // 須要排除的
+ }
};
複製代碼
運行項目,看看效果!
$ yarn server # 運行項目
# 結果
$ cross-env NODE_ENV=development webpack-dev-server --color --progress
10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.
複製代碼
🔥 Good job!🎉 🔥
└── starter
├── dist
│ ├── chunks
│ │ ├── bottom-tab-navigator.77d17027.js
│ │ ├── github.4e7f6c35.js
│ │ ├── not-found.638dbdfc.js
│ │ ├── setting.bc3fbe14.js
│ │ ├── vendors~github.7acdaa67.js
│ │ ├── vendors~github.7acdaa67.js.LICENSE
│ │ ├── vendors~main.b1a4bdbf.js
│ │ └── vendors~main.b1a4bdbf.js.LICENSE
│ ├── css
│ │ ├── bottom-tab-navigator.25c0dead.css
│ │ ├── github.866c72ba.css
│ │ ├── main.45091b7c.css
│ │ ├── not-found.d566b1be.css
│ │ └── setting.2b60ef7c.css
│ ├── fonts
│ │ ├── iconfont.63765329.woff
│ │ ├── iconfont.c2eabadd.ttf
│ │ └── iconfont.cad7bb52.eot
│ ├── images
│ │ ├── empty-data.788c1924.png
│ │ ├── logo.581fa1d8.png
│ │ └── webpage-lost.a02f7942.png
│ ├── svg
│ │ └── iconfont.1247822e.svg
│ ├── main.a010b425.js
│ └── index.html
├── node_modules
| ├── ...
| └── ...
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ ├── font
│ │ │ ├── iconfont.css
│ │ │ ├── iconfont.eot
│ │ │ ├── iconfont.svg
│ │ │ ├── iconfont.ttf
│ │ │ └── iconfont.woff
│ │ └── images
│ │ ├── empty-data.png
│ │ ├── logo.png
│ │ └── webpage-lost.png
│ ├── components
│ │ ├── BottomTabNavigator
│ │ │ ├── BottomTabNavigator.js
│ │ │ ├── BottomTabNavigator.scss
│ │ │ └── index.zh-CN.md
│ │ ├── Circle
│ │ │ ├── Circle.js
│ │ │ ├── Circle.scss
│ │ │ └── index.zh-CN.md
│ │ ├── Empty
│ │ │ ├── Empty.js
│ │ │ ├── Empty.scss
│ │ │ └── index.zh-CN.md
│ │ ├── Loading
│ │ │ ├── Loading.js
│ │ │ ├── Loading.scss
│ │ │ └── index.zh-CN.md
│ │ ├── README.md
│ │ └── RepositoriesCard
│ │ ├── RepositoriesCard.js
│ │ ├── RepositoriesCard.scss
│ │ └── index.zh-CN.md
│ ├── containers
│ │ ├── README.md
│ │ └── useRequest.js
│ ├── index.js
│ ├── router
│ │ ├── index.js
│ │ └── list.js
│ ├── services
│ │ ├── index.js
│ │ └── interface
│ │ └── github.js
│ ├── style
│ │ ├── global.css
│ │ ├── reset.css
│ │ └── variable.scss
│ ├── utils
│ │ ├── constants.js
│ │ ├── enume.js
│ │ └── tools.js
│ └── views
│ ├── Github
│ │ ├── Github.js
│ │ └── Github.scss
│ └── Setting
│ ├── Setting.js
│ └── Setting.scss
├── webpack.config.js
├── postcss.config.js
├── package.json
├── LICENSE
├── README.md
└── yarn.lock
複製代碼
項目改造到此已基本完成,但後續仍然還有工做要作 💊😯。繼續吧!
先後端分離,讓前端脫離後臺獨立開發,mock 起了很大的做用。在實際業務開發中,咱們須要一種能不侵入現有代碼,便可攔截請求,返回模擬數據。 咱們利用 json-server 幫助咱們完成這個需求。
安裝
$ yarn add -D json-server
複製代碼
新建 mock 文件夾
$ mkdir mock
$ cd mock && touch index.js
$ mkdir interface && cd interface
$ touch index.js && touch github.js
複製代碼
// starter/mock/index.js
const data = require('./interface/index');
module.exports = function Mock() {
return data;
};
// starter/mock/interface/index.js
const github = require('./github');
module.exports = {
...github,
};
// starter/mock/interface/github.js
const repositories = {
"items": [
{
"id": 6498492,
"name": "javascript",
"full_name": "airbnb/javascript",
"owner": {
"login": "airbnb",
"id": 698437,
"avatar_url": "https://avatars3.githubusercontent.com/u/698437?v=4",
},
"description": "JavaScript Style Guide",
"size": 3002,
"stargazers_count": 89966,
"watchers_count": 89966,
"language": "JavaScript",
"forks_count": 17404,
"open_issues_count": 110,
"license": {
"key": "mit",
"name": "MIT License",
},
"forks": 17404,
"open_issues": 110,
"watchers": 89966,
"default_branch": "master",
"score": 151.055
},
{
"id": 18286232,
"name": "javascript",
"full_name": "GitbookIO/javascript",
"private": false,
"owner": {
"login": "GitbookIO",
"id": 7111340,
"avatar_url": "https://avatars0.githubusercontent.com/u/7111340?v=4",
},
"description": "GitBook teaching programming basics with Javascript",
"size": 1267,
"stargazers_count": 1923,
"watchers_count": 1923,
"language": "javascript",
"forks_count": 730,
"open_issues_count": 43,
"license": {
"key": "apache-2.0",
"name": "Apache License 2.0",
},
"forks": 730,
"open_issues": 43,
"watchers": 1923,
"default_branch": "master",
"score": 104.4313
},
]
}
module.exports = {
repositories
};
複製代碼
數據來源於 GitHub ,這裏只作演示,故直接貼出數據。若是你須要動態生成數據,能夠引入 mockjs 幫助你生成數據。這裏就不作贅述了!
配置快捷命令 starter/package.json
...
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
+ "server:mock": "npm run mock & cross-env NODE_ENV=development MOCK=true webpack-dev-server --color --progress",
+ "mock": "json-server mock/index.js --watch --port 3001",
"build": "cross-env NODE_ENV=production webpack --color --progress",
"lint": "npm run lint:style && npm run lint:script",
"lint-fix": "npm run lint-fix:style && npm run lint-fix:script",
"lint:script": "eslint --ext '.js,.jsx' src",
"lint-fix:script": "npm run lint:script -- --fix",
"lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
"lint-fix:style": " npm run lint:style -- --fix",
"prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
},
}
...
複製代碼
配置 dev-server 代理
# 新建代理文件
$ cd ../.. && mkdir config
$ cd config && touch proxy.js
複製代碼
// starter/config/proxy.js
/** * @desc mock 服務代理配置 */
const MOCK_SERVER_PROXY = {
'/search/*': {
target: 'http://localhost:3001/$1',
}
}
/** * @desc 默認服務代理 */
const DEFAULT_PROXY = {};
/** * @desc dev-server 代理配置 * @param {Boolean} IS_MOCK mock 標識 * @param {Object} Proxy */
module.exports = function({ IS_MOCK }) {
if (IS_MOCK) return MOCK_SERVER_PROXY;
return DEFAULT_PROXY;
}
複製代碼
具體如何配置代理,根據接口自定!更多請參考
devServer - proxy
# starter/webpack.config.js
...
+ const IS_MOCK = process.env.MOCK === 'true';
+ const filterProxy = require('./config/proxy');
...
baseConfig.devServer = {
...
+ proxy: filterProxy({ IS_MOCK })
}
...
複製代碼
運行項目
$ yarn server:mock
# 結果
$ npm run mock & cross-env NODE_ENV=development MOCK=true webpack-dev-server --color --progress
> starter@1.0.0 mock /Users/mr.lemon/cl/CODE_CL/REACT/starter
> json-server mock/index.js --watch --port 3001
\{^_^}/ hi!
Loading mock/index.js
Done
Resources
http://localhost:3001/repositories
Home
http://localhost:3001
Type s + enter at any time to create a snapshot of the database
Watching...
10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://192.168.0.102:3000/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
ℹ 「wds」: 404s will fallback to /index.html
ℹ 「wdm」: Compiled successfully.
複製代碼
以上僅僅闡述了 mock 這一環, 關於先後端分離這裏推薦一個知乎問答 Web 先後端分離的意義大嗎?
單元測試是用來對一個模塊、一個函數或者一個類來進行正確性檢驗的測試工做。業內優秀的測試框架不少,這裏直接選擇 jest。
安裝
$ yarn add -D jest # Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
$ yarn add -D babel-jest # Jest plugin to use babel for transformation
$ yarn add -D enzyme # 一種用於 React 的 JavaScript 測試實用程序,能夠更輕鬆地測試 React 組件的輸出。您還能夠操縱,遍歷並以某種方式模擬給定輸出的運行時。
$ yarn add -D enzyme-adapter-react-16 # react 16 適配器
$ yarn add -D identity-obj-proxy # 模擬一個代理以啓用 className 查找
複製代碼
新建用於存放測試用例的文件夾及 jest 配置文件
$ touch jest.config.js
$ cd src && mkdir __tests__
$ cd __tests__
$ mkdir __mocks__ && mkdir ui && touch setup.js
$ cd __mocks__ && touch fileMock.js
$ cd ../ui && touch Loading.spec.js
複製代碼
配置 jest
<!-- starter/jest.config.js -->
module.exports = {
testRegex: '(\\.)(test|spec)(\\.)jsx?$',
// 處理靜態文件
// 樣式表和圖像等,這些文件在測試中無足輕重,由於咱們能夠安全地 mock 他們。
// 模擬 CSS 模塊,用類名查找模擬一個代理
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/src/__tests__/__mocks__/fileMock.js',
'\\.(css|scss|sass)$': 'identity-obj-proxy',
'^@/(.*)$': '<rootDir>/src/$1'
},
// 爲轉換源文件提供同步功能的模塊
transform: {
'^.+\\.(js|jsx)$': 'babel-jest'
},
// 在每次測試以前配置或設置測試環境
setupFiles: ['<rootDir>/src/__tests__/setupTests.js']
};
<!-- starter/src/__tests__/__mocks__/fileMock.js --> module.exports = 'test-file-stub'; 複製代碼
註冊 enzyme 適配器配置
// starter/src/__tests__/setup.js
import enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
enzyme.configure({ adapter: new Adapter() });
複製代碼
配置快捷運行命令
<!-- starter/package.json -->
{
...
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1",
+ "test": "jest --config jest.config.js --no-cache",
"server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
"server:mock": "npm run mock & cross-env NODE_ENV=development MOCK=true webpack-dev-server --color --progress",
"mock": "json-server mock/index.js --watch --port 3001",
"build": "cross-env NODE_ENV=production webpack --color --progress",
"lint": "npm run lint:style && npm run lint:script",
"lint-fix": "npm run lint-fix:style && npm run lint-fix:script",
"lint:script": "eslint --ext '.js,.jsx' src",
"lint-fix:script": "npm run lint:script -- --fix",
"lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
"lint-fix:style": " npm run lint:style -- --fix",
"prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
},
...
}
複製代碼
編寫測試用例
import React from 'react';
import { shallow } from 'enzyme';
import Loading from '../../components/Loading/Loading';
describe('Loading 組件基礎測試組合!', () => {
it('<Loading /> 組件默認標題應該是 "loading..."', () => {
const loading = shallow(<Loading />);
expect(loading.find('span').text()).toBe('loading...');
});
it('<Loading /> 組件標題應該是 "加載中..."', () => {
const loading = shallow(<Loading title='加載中...' />);
expect(loading.find('span').text()).toBe('加載中...');
});
});
複製代碼
這裏的用例只作演示,在實際開發中要嚴格根據 UI 組件的功能編寫用例。
運行測試
$ yarn test
# 結果
$ jest --config jest.config.js --no-cache
PASS src/__tests__/ui/Loading.spec.js
Loading 組件基礎測試組合!
✓ <Loading /> 組件默認標題應該是 "loading..." (7ms)
✓ <Loading /> 組件標題應該是 "加載中..." (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.66s
Ran all test suites.
✨ Done in 2.44s.
複製代碼
說明
try it! 🍁
在實際項目中,考慮到前端性能(首屏加載,全屏加載,白屏時間)都會對打出的資源包進行分析,而後採起相應的方案進行優化。咱們在前面論述打包構建時,已經在多個方面進行說明了,這裏不在贅述。
# 安裝
$ yarn add -D webpack-bundle-analyzer # 交互式可縮放樹圖可視化webpack輸出文件的大小。
複製代碼
# 添加相應配置 starter/webpack.config.js
...
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
if (IS_PROD) {
...
+ baseConfig.plugins.push(
+ new BundleAnalyzerPlugin()
+ );
}
...
複製代碼
$ yarn build
# 結果
$ cross-env NODE_ENV=production webpack --color --progress
98% after emitting SizeLimitsPluginWebpack Bundle Analyzer is started at http://127.0.0.1:8888
Use Ctrl+C to close it
Hash: 3bf5742858f4d53ed50d
Version: webpack 4.41.2
Time: 3079ms
Built at: 2019-10-27 21:43:54
Asset Size Chunks Chunk Names
chunks/bottom-tab-navigator.04852ffb.js 1.54 KiB 0 [emitted] [immutable] bottom-tab-navigator
chunks/github.a1f67f6e.js 4.65 KiB 1, 3 [emitted] [immutable] github
chunks/not-found.b1d86988.js 576 bytes 3 [emitted] [immutable] not-found
chunks/setting.34d43c5a.js 673 bytes 4 [emitted] [immutable] setting
chunks/vendors~github.d521d263.js 24.5 KiB 5 [emitted] [immutable] vendors~github
chunks/vendors~github.d521d263.js.LICENSE 120 bytes [emitted]
chunks/vendors~main.89c9b5d2.js 166 KiB 6 [emitted] [immutable] vendors~main
chunks/vendors~main.89c9b5d2.js.LICENSE 1.01 KiB [emitted]
css/bottom-tab-navigator.b9b2f600.css 1.54 KiB 0 [emitted] [immutable] bottom-tab-navigator
css/github.e0893952.css 1.99 KiB 1, 3 [emitted] [immutable] github
css/main.09cf98ab.css 11 KiB 2 [emitted] [immutable] main
css/not-found.091bbea2.css 64 bytes 3 [emitted] [immutable] not-found
css/setting.42e9f58f.css 252 bytes 4 [emitted] [immutable] setting
fonts/iconfont.63765329.woff 4.58 KiB [emitted]
fonts/iconfont.c2eabadd.ttf 7.52 KiB [emitted]
fonts/iconfont.cad7bb52.eot 7.69 KiB [emitted]
images/empty-data.788c1924.png 11.7 KiB [emitted]
images/logo.581fa1d8.png 8.38 KiB [emitted]
images/webpage-lost.a02f7942.png 13.5 KiB [emitted]
index.html 667 bytes [emitted]
main.02466eca.js 6.64 KiB 2 [emitted] [immutable] main
svg/iconfont.1247822e.svg 22 KiB [emitted]
Entrypoint main = chunks/vendors~main.89c9b5d2.js css/main.09cf98ab.css main.02466eca.js
複製代碼
在實際開發環境中,公司內部都會有一套持續集成的東西幫助咱們去部署。這裏爲了演示方便,咱們採用 Travis CI 幫助咱們去作這個事情。
Travis CI: 代碼有變動,自動運行構建和測試,並反饋運行結果。
如何使用配置?請參考 持續集成服務 Travis CI 教程
本項目的 yml 腳本,請參考項目
預覽 「preview」