前言
搭一個腳手架真不是一件容易的事,之前爲了學習webpack是怎麼配置的選擇自己搭建開發環境,折騰了好幾天總算對入口文件、打包輸出、JSX, es6編譯成es5、css加載、壓縮代碼等這些基礎的東西有了一個大體的瞭解。後來有一次組內分享技術,我作死的選擇了webpack,爲了看起來高大上又去折騰它的按需加載、怎樣處理第三方插件、拆分CSS文件、利用Happypack實現多進程打包等等。徹底把自己搞暈了。再後來接手了一個緊急的項目,實在來不及去折騰webpack了,就選擇使用react官方推薦的腳手架create-react-app,這個腳手架確實搭的非常完善,幾乎不需要我們修改配置,我也研究了一下它的配置,準備從零開始搭建一個react+webpack的開發環境,配置從簡單到複雜,由於內容較多,我將分爲幾篇文章講述,這是第一篇。
另外,熱更新我單獨寫成一篇文章了,當你修改一次代碼就需要手動啓動服務器,然後你煩了的時候,你可以先去把熱更新配置好再回來繼續:開始一個React項目(二) 徹底弄懂webpack-dev-server的熱更新
初始化
先貼出項目結構
my-app/
|
--- README.md
--- package.json
--- webpack.config.js
--- public/
|
--- index.html(模板文件)
--- favicon.ico(網站圖標)
--- src/(項目文件都在這裏)
|
--- index.js(入口文件)
--- pages/ (頁面)
--- components/(抽離的公用組件)
--- css/
--- js/
--- images/
一開始最重要的需要你建好的文件是public/index.html
和src/index.js
。
新建一個項目,使用npm init
初始化生成一個package.json文件。可以全部回車,後面反正是可以修改的。
安裝webpack: npm install webpack --save-dev
全局安裝: npm install webpack -g
(全局安裝以後纔可以直接在命令行使用webpack)
一個最簡單的webpack.config.js文件可以只有entry(入口文件)和output(打包輸出路徑)
新建webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', //相對路徑
output: {
path: path.resolve(__dirname, 'build'), //打包文件的輸出路徑
filename: 'bundle.js' //打包文件名
}
}
新建入口文件 src/index.js
function hello() {
console.log('hello world');
}
好了這就夠了,我們已經可以運行這個項目了,打開命令窗口試一下:webpack
編譯成功了,項目根目錄下已經生成好build/bundle.js文件了,bundle.js文件前面的幾十行代碼其實就是webpack對怎麼加載文件,怎麼處理文件依賴做的一個聲明。
我們可以將啓動wepback的命令寫到package.json中並添加一些有用的參數:
package.json
"scripts": {
"start": "webpack --progress --watch --hot"
},
progress
是顯示打包進程,watch
是監聽文件變化,hot
是啓動熱加載,更多命令行參數詳見:webpack cli
以後只需要執行npm start
就可以了。
添加模板文件index.html
配置react項目最重要的兩個文件是入口文件(這裏是src/index.js)和html模板文件(這裏是public/index.html),入口文件是整個項目開始運行的地方,模板文件是構建DOM樹的地方,相信有一部分小夥伴在網上看到的教程是直接在打包路徑build裏面建一個index.html,然後手動或者使用html-webpack-plugin
將打包後的js引入,這樣存在一個問題是build本身是打包路徑,而這個路徑的所有文件都不應該是我們手動去添加的,甚至包括這個文件夾也不是我們事先建好的。所以最好是按照create-react-app
的方式,將這類不需要被webpack編譯的文件放到public路徑下。
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
現在要讓webpack知道這就是我們的html入口文件,並且我們不需要手動引入打包後的js文件,需要安裝html-webpack-plugin
:
npm install html-webpack-plugin --save-dev
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html', //指定模板路徑
filename: 'index.html', //指定文件名
})
]
}
plugins是用於擴展webpack功能的,它幫助webpack構建項目,比如上面的
html-webpack-plugin
自動生成模板文件,以及後面用到的分離CSS等。
這裏提示一下生成的HTML路徑就是output.path
指定的路徑,不僅如此,像extract-text-webpack-plugin
分離CSS文件打包的文件路徑也是這個路徑。
重新運行一下:npm start
現在可以看到build路徑下已經生成好了一個index.html文件,並且這個文件已經引入了bundle.js文件了。
開始React項目
安裝: npm install react react-dom --save-dev
,現在來修改我們的入口文件
src/index.js
import React, { Component } from 'react';
import ReactDom from 'react-dom';
class App extends Component {
render() {
return <h1> Hello, world! </h1>
}
}
ReactDom.render(
<App />,
document.getElementById('root')
)
彆着急運行,react裏面的JSX語法普通瀏覽器可解析不了,需要安裝babel來解析:
npm install babel babel-cli babel-loader --save-dev
再安裝兩個分別用於解析es6和jsx語法的插件:
npm install babel-preset-env babel-preset-react --save-dev
注:以前編譯es6以上語法用的是
babel-preset-es2015
,現在是時候說再見了,babel-preset-env
是一個更定製化的插件,你可以指定你要兼容的瀏覽器版本,這樣babel會選擇編譯該版本不支持的語法而不是全部編譯成舊的語法,具體配置參見:babel-preset-env
webpack.config.js
module.exports = {
...
module: {
loaders: [ //配置加載器
{
test: /\.js$/, //配置要處理的文件格式,一般使用正則表達式匹配
loader: 'babel-loader', //使用的加載器名稱
query: { //babel的配置參數,可以寫在.babelrc文件裏也可以寫在這裏
presets: ['env', 'react']
}
}
]
}
}
webpack最重要的配置都在modules(模塊)裏,loaders(加載器)是處理源文件的,後面你會看到,loader可以處理不同的js(jsx, es6等)編譯成js,less等編譯成css,將項目中引用的圖片等靜態資源路徑處理成打包以後可以正確識別的路徑等。
現在試着運行一下,沒有報錯的話,直接雙擊打開build/index.html
就可以看到hello world!
了。
加載和解析CSS樣式
我們以前寫CSS大致是兩種方式,一寫在html裏,二寫在CSS文件裏。現在我們沒有html只有JSX了,JSX通俗一點理解就是可以在js裏面寫html,所以我們如果要在jsx裏面寫css,就是在js裏面寫css,寫過RN的小夥伴應該對這種寫法比較熟悉。
//方式一:
const styles = {
container: {
textAlign: 'center',
marginTop: '50px'
},
containerBtn: {
margin: '0 20px',
backgroundColor: '#dde18e'
}
}
//使用:
<div style={styles.container}>
<button style={styles.containerBtn}>count+1</button>
</div>
//方式二:
<div style={{textAlign: 'center', marginTop: '50px'}}>
</div>
而如果想在JSX文件裏面像我們以前的用法一樣去引入CSS文件,就只能使用import語句,但是import引入的都會被當做js處理,所以,我們需要對css文件做一個轉換。這就需要css-loader
和style-loader
,其中css-loader
用於將css轉換成對象,而style-loader
則將解析後的樣式嵌入js代碼。
安裝:npm install style-loader css-loader --save
webpack.config.js:
loaders: [
{
test: /\.css/,
loader: 'style-loader!css-loader'
},
]
使用方式三:
//index.css
.container {
text-align: center;
margin-top: 40px;
}
//index.js
import '../css/index.css
<div className="container">
</div>
需要注意用className
而不是class
。
單獨編譯CSS文件(只在生產環境配置)
一般發佈到線上以後,爲了加載速度更快會把CSS和JS打包到不同的文件中,使用extract-text-webpack-plugin
插件可以分離CSS。而其實,開發的時候是不需要單獨編譯CSS文件的。如果你在開發環境加了這個,又配置了熱更新,那麼你會發現CSS發生變化時熱更新用不了了,所以建議開發環境就不要配置這個了。
npm install extract-text-webpack-plugin --save
webpack.config.js
文件:
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');
module.exports = {
//...
module: {
loaders: [
{
test: /\.css/,
use: ExtractTextWebpackPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html'
}),
new ExtractTextWebpackPlugin("bundle.css")
],
};
使用PostCSS或者Less, Sass等CSS工具
Less或Sass想必大家都非常熟悉了,PostCSS可能有的小夥伴沒有用過,我也是在create-react-app
的配置裏第一次見到,然後就去搜了一下,發現它挺強大的。它不是什麼預處理後處理語言,而是一個平臺,這就像Node一直宣稱的那樣:我是平臺!我是平臺!我是平臺!既然是個平臺,那我們就可以在平臺上做很多事情,比如說:檢查CSS(像eslint檢查js那樣)、添加瀏覽器前綴(該平臺目前最火的插件)、使用未來的CSS語法(大概就像現在的花唄??)、函數式語法(類似Sass語法)等等。目前像阿里爸爸,維基百科等都在使用它。我覺得雖然官方介紹了很多插件,但我們用其中的幾個就可以了。
之前寫過Sass或Less的童鞋估計會問:既然它是個平臺那我可以在它上面寫Sass(Less)嗎?答案是可以的,另外,它也有一個類似於Sass語法的插件,在它上面配置起來非常容易,所以,怎麼選擇看你咯。
安裝:npm install postcss-loader --save
安裝一些你需要的工具:npm install autoprefixer precss postcss-flexbugs-fixes --save
注:autoprefixer是自動添加瀏覽器前綴的插件,precss是類似Sass語法的css插件,postcss-flexbugs-fixes是修復了flex佈局bug的插件,具體會有哪些bug你可以自行查看。
webpack.config.js
文件:
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer'),
require('precss'),
require('postcss-flexbugs-fixes')
]
}
}
]
},
測一下配置生效了沒有
src/css/style.css
:
$mainColor: #8ce7b4;
$fontSize: 1rem;
@keyframes rotate {
0% {transform: rotate(0deg);left:0px;}
100% {transform: rotate(360deg);left:0px;}
}
button {
background: $mainColor;
font-size: calc(1.5 * $fontSize);
}
.container .logo {
animation: rotate 10s linear 0s infinite;
}
可以看到已經自動幫我們添加了前綴以及可以使用sass語法了,而且這是在css文件裏直接寫,不需要建其他後綴的文件。
如果你非要用less或者sass,也可以,但我還是會建議你保留postcss,畢竟它有很多有用的插件,只是去掉precss即可。安裝:npm less less-loader --save
{
test: /\.(css|less)$/,
use: [
//...
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer'),
require('postcss-flexbugs-fixes')
]
}
},
{
loader: 'less-loader',
}
]
},
好了,現在你可以寫less了。
加載圖片資源
我們知道webpack打包會將所有模塊打包成一個文件,而我們在開發項目時引入圖片資源的時候是相對於當前文件的路徑,打包以後這個路徑是錯誤的路徑,會導致引入圖片失敗,所以我們需要一個處理靜態資源的加載器,url-loader和file-loader。我看到網上說url-loader是包含了file-loader的功能的,所以我們只需要下載url-loader就可以了,但是我下載完以後它卻提示我url-loader依賴file-loader,並且運行項目會報錯,所以我又下載了file-loader。url-loader有一個功能是對於指定大小以下的圖片會轉成base64,這樣可以有效減少http請求。
安裝:npm install file-loader url-loader --save
webpack.config.js
{
test: [/\.gif$/, /\.jpe?g$/, /\.png$/],
loader: 'url-loader',
options: {
limit: 10000, //1w字節以下大小的圖片會自動轉成base64
},
}
使用圖片有兩種情況,一在CSS裏面設置背景,二在JSX裏面設置背景或<src>
,
CSS裏面和以前的使用方式一樣,假如你的目錄結構長這樣:
src
|
---pages/
--- css/
--- images/
那麼在CSS文件裏引入背景圖的路徑就爲:
.container {
background: url('../images/typescript.jpeg') no-repeat center / contain;
}
在JSX裏面引入圖片有幾種方式:(該頁面在pages/下)
//方式一:
import tsIcon from '../images/typescript.jpeg';
//方式二:
const tsIcon = require('../images/typescript.jpeg');
//使用方式一:
<img src={tsIcon} alt="" />
//使用方式二:
<div style={{background: `url(${tsIcon}) no-repeat`}}></div>
//使用方式三:
const styles = {
test: {
background: `url(${tsIcon}) no-repeat center`
}
}
render() {
return (
<div style={styles.test}></div>
)
}
另外,你也可以測試一些小的icon,看看是不是轉換成了很長的一串字符串。
配置ESLint
js的鬆散真的是體現在方方面面,現在除了有TypeScript這種一心想用C#風格幫助js走上人生巔峯的語言,也有ESLint這種控制規則從娃娃抓起的工具,ESLint的創始人可是js紅皮書的作者,所以,趕緊用起來吧,這樣你就完全不必和隊友爭論到底Tab鍵設置爲4還是爲2,加不加分號等一系列問題啦。
安裝:npm install eslint eslint-plugin-react eslint-loader --save
注:可以全局安裝eslint,這樣你纔可以直接在命令行使用
eslint xxx
,如果選擇全局安裝eslint那麼你使用的任何插件或可分享的配置也都必須在全局安裝。如果選擇本地安裝,命令行使用應該是./node_modules/.bin/eslint xxx
eslint-plugin-react是檢查react項目的插件,eslint-loader是webpack需要的加載器插件。
webpack.config.js
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['env', 'react'], //babel編譯es6以上語法以及jsx語法
plugins: ["react-hot-loader/babel"]
}
},
{
test: /\.js$/,
enforce: 'pre', //加載器的執行順序,不設置爲正常執行,pre(前)|post(後),eslint是檢查代碼規範,應該在編譯前就執行
loader: 'eslint-loader',
},
]
接下來需要配置ESLint規則,爲了滿足大家“稀奇古怪”的代碼風格,ESLint遵循各種規則自定義的原則,所以,前面我說有了它就可以避免因代碼風格不同而與隊友發生爭執的問題是不準確的,因爲,我們還是要制定規則啊。對於我等小菜鳥來說,遵守別人的規則會比自己制定好一些,因爲,怕你對自己太好了。
我們先來看看如何配置吧,採用命令行來初始化:eslint --init
,如果是本地安裝的話:./node_modules/.bin/eslint --init
遇到的第一個問題:你喜歡怎麼配置你的ESLint呢?
- 根據你喜歡的方式來制定規則,你需要回答一些問題。
- 使用當下流行的代碼規則。
- 根據你的js文件生成一些規則。
使用當下流行的代碼規則就是用大公司制定的一套規則啦,這裏只有三個選項:
如果你都不喜歡,沒關係,這裏有很多款式供你選擇,比如eslint-config-react-app就是create-react-app
的代碼規則。據說目前最常用的是Airbnb,它也被稱爲史上最嚴規則,選擇這個回車,接着回答問題:是否使用React,希望生成的eslint文件格式是什麼,我選擇的是javascript,Airbnb需要安裝一些插件,耐心等待就好。
好了,運行一下代碼,沒有意外的話你的代碼肯定有很多報錯,反正我一共就兩js文件,加起來七十多行代碼,報了九十多個錯,哈哈哈哈,讓我先冷靜冷靜換個姿勢,選擇自定義規則,別誤會,我是打算另外寫一篇文章專門來解決我那九十多行的報錯的,寫在這裏篇幅有點太長了。
現在選擇第一種方式,自定義代碼規則,ESLint附帶了一些默認規則幫你起步,具體查看默認規則列表,打勾的表示已經啓用的規則,然後另外還需要你回答一些問題:
這裏有幾個要注意的選項:
注意事項一:indentation(縮進)是用tab還是空格
意思不是你縮進的時候是按空格鍵縮進還是tab鍵縮進,或者說很多時候縮進是編輯器做的事情,我們要做的是告訴編輯器是用哪種方式,而怎麼看的出來用的是什麼呢?以sublime編輯器爲例,當你選中代碼的時候,縮進的樣式就出來了:
其中,..是空格,—是tab,如何修改文件的縮進風格呢?sublime編輯器是選擇View–> Indentation,首先,你可以選擇tab的寬度是多少,一般是2,4,然後,如果你想修改當前已有的文件縮進風格,選擇 Convert Indentation to Tabs或者 Convert Indentation to Spaces,這樣,整個文件的縮進風格就統一了,並且,以後你新建的文件也會按照這個風格,但是!你已有的文件風格不會變,你需要手動去每個文件下修改。所以,配置這種檢查工具肯定是越早越好,等你寫了一大半了再跑回來配置看到幾千個錯誤都是很正常的,而這時候,估計你會選擇放棄。
注意事項二:line endings 選擇
瞭解Windows和Unix系統的童鞋都知道,這倆系統的行尾結束符不一樣,Unix的行尾是兩個字符:”\r\n”,Windows的行尾是一個字符:”\n”,而如果假如你在mac裏寫代碼這裏卻選擇了Windows,你就會看見Expected linebreaks to be 'CRLF' but found 'LF'
,同理反過來也一樣,所以不要選錯了。
注意事項三:semicolons(分號)的選擇
分號在js裏面實在是個很隨意的東西,有的人喜歡打,有的人不喜歡打,有的人喜歡打一部分,但是這個選擇卻引起了很多爭議,有人認爲,雖然js有自動分號插入 (ASI)的功能,但總是依賴js去幫我們打分號是不可靠的,首先,js也會累是吧,其次,有些地方js也不知道該打在哪裏,然後它就會亂打,然後你懂的,你就會被噴。有人認爲,不打分號可以節省編譯時間,而且,看起來很棒。然後就有一羣和事佬跳出來說:我可以在可能會引起js困惑的地方打分號,在js可以自動引入分號的地方省略,嗯,應該說我們大部分人都屬於這一類人。
在react裏面,如果選擇了始終使用分號就會有一些比較困惑的地方,比如:
add() {
this.setState((preState) => {
return{
count: preState.count + 1
}
})
}
render() {
return (
<div>hello </div>
)
}
那麼,ESLint會報return{}後缺少分號,this.setState({})後缺少分號,甚至render裏的return()後也缺少分號,而這應該是大部分人都不會選擇加分號的地方,所以,始終使用分號這個選項在react項目裏恐怕不是那麼適用。不過,你是自由的,你的代碼風格由你決定。
現在你應該有能力解決Missing semicolon (或Extra semicolon )
,Expected indentation of 1 tab but found 4 spaces
,Mixed spaces and tabs
等這種問題了。
但是ESLint依然對react非常不友好啊,比如:'React' is defined but never used
,或者是引入其他組件也會報這個警告,當然,引入了又沒有用或者根本沒有引入某個組件,報了警告是非常正常的,但是我們明明用到了引入的東西它還是報警告這就說不過去了,修改.eslintrc.js文件:
"extends": [
"eslint:recommended",
"plugin:react/recommended" //增加
],
這絕對是最萬能的解決辦法!!相信大家以前用過這種寫法:
"rules": {
//...
"react/jsx-uses-react": 1,
}
但這個只能解決'React' is defined but never used
這個錯誤,如果是引入其他組件比如import Home from './pages/Home'
依然會報'Home' is defined but never used
,而這時候你還需要添加一句:"no-unused-vars": 1
纔可以,所以用第一種方法是最好的。
寫在最後
我把該項目放在github上了。雖然這是webpack配置的第一篇,但是爲了開發方便,我把webpack-dev-server的熱更新配置也放進來了,如果你對熱更新有疑問可以閱讀開始一個React項目(二) 徹底弄懂webpack-dev-server的熱更新。