前言
之前寫了一篇媽媽再也不用擔心我不會webpack了,這次繼續對其進行補充。本文依舊是遵循直觀易懂的規則進行書寫。希望對大家有幫助。如果不太熟悉webpack可以先看看之前的文章媽媽再也不用擔心我不會webpack了。下面我們由淺入深來介紹webpack的使用
一些你不知道是什麼意思的東西(題外話)
import path = require('path');
- path.join
- path.resolve
- __dirname
path其實是node中的一個模組,下面我們就將一下這幾個常見的東西。
path.join
path.join其實是對路徑進行拼接。
const path = require('path');
let str1 = path.join('./path/./', './upload', '/file', '123.jpg');
console.log(str1); // path/upload/file/123.jpg
let str2 = path.join('path', 'upload', 'file', '123.jpg');
console.log(str2); // path/upload/file/123.jpg複製程式碼
使用了它之後就得到了一個拼接好的路徑
path.resolve
它是絕對路徑的操作
let myPath = path.resolve('path1', 'path2', 'a');
console.log(myPath); // E:/workspace/NodeJS/path1/path2/a複製程式碼
它的結果是絕對路徑,不懂絕對路徑和相對路徑的同學去查下相關知識哦。
這部分到這就結束了,下面可能涉及到這幾個東西的使用。
__dirname
使用__dirname變數獲取當前模組檔案所在目錄的完整絕對路徑
因為下面可能會碰到這幾個東西,所以我們稍微簡單提了一下。題外話到此結束,我們現在進入正題。
resolve配置
resolve.extensions
我們在編輯器上開發專案程式碼,但這個傻慫編輯器IDE功能並不是很強,每次import進來的檔案都沒有字尾名。如果你使用的是腳手架工具,你可能會發現一個有趣的地方,就是當我們引入js、jsx或者vue檔案的時候,我們不需要加字尾就可以使用了。但是你寫了一些less或者sass引入到檔案中,也沒有新增字尾,編譯直接報錯。這個其實就是resolve的問題。
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".ts", ".tsx", ".js", ".json"]
}複製程式碼
對resolve進行配置能設定模組如何被解析。
這裡的extensions就是字尾的使用,我這裡預設是ts/tsx/js/json,這些檔案在import時,不新增字尾是可以的,只需要在陣列中新增你想要省略的字尾名就可以達到同樣的效果。比如下面這種
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx', '.less']複製程式碼
現在就算編輯器不給你新增字尾,你也不需要再加上字尾了,是不是省了很多事。
resolve.alias
這裡是建立 import 或 require 的別名,來確保模組引入變得更簡單
resolve: {
alias: {
Utilities: path.resolve(__dirname, 'src/utilities/'),
Temp: path.resolve(__dirname, 'src/templates/')
}
}複製程式碼
之前你引入src/template裡面的1檔案可能是這樣
import 1 from '../src/template/1';複製程式碼
現在你可以這樣寫了
import 1 from 'Temp/1';複製程式碼
這裡的路徑還是具體看你的檔案路徑。不要照抄照搬哦。
這兩項是我個人覺得使用頻率比較多的,其他情況請去官方文件檢視下。
全域性變數的使用
使用全域性變數進行url的替換
現在專案還是在開發階段,你可能通過下面的介面獲取資訊
http://www.xxx.com/test/v3 + 具體介面複製程式碼
這個介面是放在測試伺服器上的,但專案一旦上線要使用線上伺服器
http://www.xxx.com/api/v4 + 具體介面複製程式碼
你於是冒出了一個很傻X的想法,本地開發或者測試時使用上面的介面,等到上線的時候再將它改掉。鬼鬼,我們不能這麼秀。我給你提供一個好方法。
使用DefinePlugin外掛來建立全域性變數來解決這個問題
DefinePlugin允許你建立一個在編譯時可以配置的全域性常量,我們下面建立一個名為url的全域性變數,如果你是將開發和生產環境的webpack配置檔案分開,你可以這樣寫
開發環境
plugins: [
new webpack.DefinePlugin({
url: JSON.stringify('http://www.xxx.com/test/v3')
})
]複製程式碼
生產環境
plugins: [
new webpack.DefinePlugin({
url: JSON.stringify('http://www.xxx.com/api/v4')
})
]複製程式碼
如果你只有一個webpack配置檔案,你也可以寫成這樣
plugins: [
new webpack.DefinePlugin({
url: process.env.NODE_ENV === 'production' ?
JSON.stringify('http://www.xxx.com/api/v4') :
JSON.stringify('http://www.xxx.com/test/v3')
})
// 這裡其實涉及到一個問題,就是你在生產環境的時候必須增加命令修改process.
// env.NODE_ENV = production,否則上面的程式碼是不生效的
// 在package.json的scripts物件中可以使用,使用方法見我上一篇webpack的文章
]複製程式碼
由於這個變數必須包含字串引號,所以你要麼使用'"你的變數內容"', 或者使用 JSON.stringify('你的變數內容')這種形式。
現在你在專案中的介面url就可以寫成這樣了
`${url}/介面資訊` // es6的字串模板應該都懂吧?複製程式碼
你console.log(url)也是可以的哦,現在我們就解決了這個噁心的問題。
但是還有更噁心的問題等著我們,哈哈。真滴煩!!!
如果你的專案是腳手架搭建,往往會有eslint,eslint不進行設定是沒辦法使用這個全域性變數的,找到eslint配置檔案,新增如下程式碼
"globals": {
"go": true
}複製程式碼
多入口檔案打包
為什麼要設定多檔案入口打包
在很多情況下,我們都是用當下流行的框架進行web開發,比如vue、react。在開發一段時間過後,測試ok,我們準備build專案了,但是打包之後檔案是4.5MB,這玩意對pc或者移動來說都不是一個很好的體驗。如果我們不去管它,那每次我們改版或其他的一些情況,使用者都需要去重新載入4.5mb的檔案,哪怕你只是修改了一行程式碼。
多入口實現第三方庫檔案的單獨打包
使用多入口檔案配合外掛解決此問題
entry: {
vendor: ['react', 'react-dom'],
app: "./src/index.tsx"
}複製程式碼
這裡我們設定了兩個入口,一個是app,就是我們傳統使用的入口檔案。而vendor使用的是一個陣列,我們把react和react-dom單獨提取出來進行打包。這些庫我們基本上是不會改動原始碼的,如果我們把它們單獨打包出來,即便我們修改了專案的程式碼,react和react-dom的程式碼都不需要變,這時瀏覽器都直接讀取vendor檔案的快取就可以了,減少了每次載入資源的體積,增強了使用者體驗。
配合外掛CommonsChunkPlugin使用
只是增加入口檔案是不管用的,我們需要使用外掛把vendor檔案從app檔案中剝離出來
plugins: [
new webpack.optimize.CommonsChunkPlugin('vendor')
]複製程式碼
現在我們已經把vendor和app檔案分割了。這裡只是舉了一個簡單的栗子,小夥伴可以根據自己的需求自己進行配置。
我們也可以把公共元件進行一個單獨的打包,這裡不再贅述,感興趣的小夥伴可以自己試驗哦。
html-webpack-plugin
之前的文章其實已經介紹過這個外掛了,但這次我們稍微具體的說一下。我們使用腳手架生成的index.html其實你是找不到script標籤引入js檔案的,可能你也想這樣做,自己動手引入真的麻煩。html-webpack-plugin來幫你
plugins: [
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({
inject: true, // 這個配置項為true表示自動把打包出來的檔案通過自動生成script標籤新增到html中
template: index.html, // 模板檔案,其實如果沒有特殊要求,可以考慮就是用原本的html檔案,不再單獨建立模板。
minify: { // 壓縮的配置,感興趣的同學意義自己查下意思
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
})
]複製程式碼
你的模板html檔案是這樣
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
</head>
<body>
<div id="example"></div>
<!-- Main -->
</body>
</html>複製程式碼
通過使用上面的html-webpack-plugin配置它就變成了這樣
<!DOCTYPE html><html><head><meta charset="UTF-8"/><title>Hello React!</title></head><body><div id="example"></div><script type="text/javascript" src="vendor.19786c9df38012fdca96.js"></script><script type="text/javascript" src="app.19786c9df38012fdca96.js"></script></body></html>複製程式碼
這樣其實就是和你用腳手架搭建的一毛一樣了。
講一些專案中會出現的問題--devServer的使用
我們在使用webpack時,常常會用到webpack-dev-server。它給我們提供了一個server,使我們的專案能跑server上。
historyApiFallback
現在專案在本地正常執行,你看了下位址列
localhost:8080/#複製程式碼
leader說#是什麼鬼?必須去掉,你見過誰的網址帶#?
這個#其實是hash路由進行路由跳轉的依託。它是可以去掉的。react4是使用BroswerRouter替換HashRouter即可,vue的話同學去查一下即可。去掉#之後我們使用的就是h5的history模式進行路由跳轉了。
你以為這樣就行了麼?
但是老闆又有新要求了,我們的頁面不是放在根目錄下。網址是這樣www.xxx.com/xxx,這個時候你要想頁面放在伺服器上能正常使用就需要在index.html新增base標籤。
你信心滿滿的進行試驗,發現,我擦,報錯了,連頁面都找不到了。
這個原因是因為現在在這個路徑下我們是找不到資原始檔,會報404的問題,這個時候我們必須要設定
devServer: {
historyApiFallback: true
}複製程式碼
這個東西就是告訴webpack-dev-server,再找不到檔案的時候預設指向index.html,底層的東西我並沒有去深入查詢,感興趣的同學可以去查下。
有些情況下,我們可能會起不止一個服務,這個時候埠往往就會衝突,新增port屬性,修改下埠即可解決衝突問題。
devServer: {
historyApiFallback: true,
port: 1234
}複製程式碼
現在,地址就變成了localhost: 1234了。
devServer其實還有很多配置項,感興趣的同學可以去官網檢視
webpack的熱替換問題
熱替換俗稱HMR,即只更新你修改的區域性內容,而不重新整理整個頁面,大大提高開發效率。這個只能在開發時使用哦,下面簡單說一下用法。後面直接說react和vue的使用。
const webpack = require('webpack');
module.exports = {
entry: {
app: './src/index.js'
},
devServer: {
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]複製程式碼
下面是index.js檔案需要的配置(這裡覺得比較雞肋,因為如果你不把js全寫在一起,你就要每個檔案都要新增這個東東。)
+ if (module.hot) {
+ module.hot.accept('./app.js', function() {
+ console.log('Accepting the updated printMe module!');
+ printMe();
+ })
+ }複製程式碼
module.hot.accept 接受兩個引數,第一個引數是修改的檔案,第二個是會掉函式。這裡只要app.js修改,就會觸發回撥。
我們大多數情況下是使用react或者vue的,在這種情況下,你使用webpack的HMR是不起作用的。因為它無法儲存這些框架的狀態。
react和vue給我們提供瞭解決方案
- react-hot-loader
- vue-loader
同學們可以在npm中搜尋這兩個東西的用法,都有對應的使用。
此部分webpack配置要去npm找到對應的loader看,很簡單。我主要講我當時很困惑的一點。
我主要講一下react的,就是在使用路由和redux的情況下,我們的index.js檔案應該怎麼寫。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { AppContainer} from 'react-hot-loader'
import registerServiceWorker from './registerServiceWorker';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from "./reducer";
import "./index.css";
import App from "./App";
import './style/style';
// redux的配置,可以忽略
const store = createStore(reducers, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : ()=>{}
));
registerServiceWorker();
使用AppContainer包裹根元件即可
const render = Component => {
ReactDOM.render(
<AppContainer>
<Component store={store}/>
</AppContainer>,
document.getElementById('root')
)
};
render(App);
// App作為內容的根元件,redux和router全部放在裡面,只要有內容修改,就呼叫render函式
if (module.hot) {
module.hot.accept('./App', () => { render(App) })
}複製程式碼
App檔案
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import Second from "./second";
import Third from "./Third";
import Header from "./header";
import First from "./first";
class App extends Component {
render() {
return (
<Provider store={this.props.store}>
<BrowserRouter className="App">
<div>
<Header />
<Switch>
<Route exact path="/" component={First}/>
<Route path="/second" component={Second}/>
<Route path="/third" component={Third}/>
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;複製程式碼
我告你,現在你開發,根本不需要頁面重新整理,超爽的。修改哪裡,哪裡變化。謝謝大家