深入淺出的webpack構建工具---DevServer配置項(二)
閱讀目錄
- DevServer配置項
1. contentBase
該配置項指定了伺服器資源的根目錄,如果不配置contentBase的話,那麼contentBase預設是當前執行的目錄,一般是專案的根目錄。
可能如上解析還不夠清晰,沒有關係,我們下面還是先看下我整個專案的目錄結構,然後進行相關的配置,使用contentBase配置項再來理解下:
### 目錄結構如下: demo1 # 工程名 | |--- dist # dist是打包後生成的目錄檔案 | |--- node_modules # 所有的依賴包 | |--- js # 存放所有js檔案 | | |-- demo1.js | | |-- main.js # js入口檔案 | | | |--- webpack.config.js # webpack配置檔案 | |--- index.html # html檔案 | |--- styles # 存放所有的css樣式檔案 | |--- .gitignore | |--- README.md | |--- package.json
index.html 程式碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link href="dist/main.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="app"></div> <script src="dist/bundle.js"></script> </body> </html>
main.js 程式碼如下:
require('../styles/main.css');
import demo1 from './demo1.js';
demo1.js 程式碼如下:
console.log(111);
webpack配置程式碼如下:
const path = require('path'); // 提取css的外掛 const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: './js/main.js', output: { filename: 'bundle.js', // 將輸出的檔案都放在dist目錄下 path: path.resolve(__dirname, 'dist'), publicPath: '/dist' }, mode: 'development', module: { rules: [ { // 使用正則去匹配要用該loader轉換的css檔案 test: /\.css$/, loaders: ExtractTextPlugin.extract({ // 轉換 .css檔案需要使用的Loader use: ['css-loader'] }) }, { test: /\.(png|jpg)$/, loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]' } } ] }, resolve: { // modules: ['plugin', 'js'] }, plugins: [ new ExtractTextPlugin({ // 從js檔案中提取出來的 .css檔案的名稱 filename: `main.css` }) ] };
package.json 配置程式碼如下:
"scripts": { "dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline", "build": "webpack --progress --colors" }
執行 npm run dev後,一切正常成功後,在瀏覽器下 執行 http://localhost:8080/ 即可在控制檯看到 列印出 111 了。
如上是沒有使用devServer配置的情況下。 下面我們來看下使用 devServer配置.
在webpack配置加上如下配置,即配置項指定了伺服器資源的根目錄。比如我們打包後的檔案放入 dist目錄下。
module.exports = { devServer: { contentBase: path.join(__dirname, "dist") }, }
如上配置完成後,我們再執行 npm run dev, 再在位址列中 執行 http://localhost:8080/ 後看到如下資訊:
也就是說 配置了 contentBase後,伺服器就指向了資源的根目錄,而不再指向專案的根目錄。因此再訪問 http://localhost:8080/index.html 是訪問不到的。但是訪問 http://localhost:8080/bundle.js 該js檔案是可以訪問的到的。
2. port
該配置屬性指定了開啟伺服器的埠號,比如如下配置:
module.exports = { devServer: { contentBase: path.join(__dirname, "dist"), port: 8081 }, }
配置完成後,再執行打包命令 npm run dev 後,可以看到如下圖所示:
現在我們可以通過 如下地址 http://localhost:8081/ 也可以訪問了,也就是說 通過port配置,埠號從預設的8080改成8081了。
3. host
該配置項用於配置 DevServer的伺服器監聽地址。比如想讓區域網的其他裝置訪問自己的本地服務,則可以在啟動DevServer時帶上 --host 0.0.0.0.
host的預設值是 127.0.0.1, 下面我們也簡單的配置下 host 屬性。
module.exports = { devServer: { contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0' } }
配置完成後,再執行打包命令 npm run dev 後,可以看到如下圖所示:
我們訪問 http://0.0.0.0:8081/ 可以訪問的到了,其他區域網的同學應該也能訪問的到吧。
4. headers
該配置項可以在HTTP響應中注入一些HTTP響應頭。 比如如下:
module.exports = { devServer: { contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' } } }
如上配置完成後,打包下,重新整理下瀏覽器,可以看到請求頭加了上面的資訊,如下所示:
5. historyApiFallback
該配置項屬性是用來應對返回404頁面時定向跳轉到特定頁面的。一般是應用在 HTML5中History API 的單頁應用,比如在訪問路由時候,訪問不到該路由的時候,會跳轉到index.html頁面。
我們現在在dist目錄下 新建一個index.html, 程式碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app">歡迎你們來訪問我</div> </body> </html>
為了使配置項生效,我們只需要設定該 屬性值為true即可; 如下配置:
module.exports = { devServer: { contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, historyApiFallback: true }, }
現在我們來訪問 http://0.0.0.0:8081/home 這個不存在的路由時,會發生什麼?如下所示:
如上可以看到,當不存在該路由的時候,通過該配置項,設定屬性值為true的時候,會自動跳轉到 index.html下。
當然如上只是簡單的配置下,當然我們也可以手動通過 正則來匹配路由,比如訪問 /user 跳轉到 user.html,訪問 /home 跳轉到 home.html, 如下配置:
當然我們需要在 dist 目錄下 新建 home.html 和 user.html 了,如下基本配置:
module.exports = { devServer: { contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, historyApiFallback: { // 使用正則來匹配路由 rewrites: [ { from: /^\/user/, to: '/user.html' }, { from: /^\/home/, to: '/home.html' } ] } }, }
重新執行打包下, 繼續訪問 http://0.0.0.0:8081/home 和 http://0.0.0.0:8081/user 即可看到能訪問得到對應的頁面了。
6. hot
該配置項是指模組替換換功能,DevServer 預設行為是在發現原始碼被更新後通過自動重新整理整個頁面來做到實時預覽的,
但是開啟模組熱替換功能後,它是通過在不重新整理整個頁面的情況下通過使用新模組替換舊模組來做到實時預覽的。
我們可以在 devServer中 配置 hot: true 即可:如下配置程式碼:
module.exports = { devServer: { contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, historyApiFallback: { // 使用正則來匹配路由 rewrites: [ { from: /^\/user/, to: '/user.html' }, { from: /^\/home/, to: '/home.html' } ] }, hot: true } }
當然我們也可以在scripts命令列中配置,比如我專案中在package.json中的scripts配置如下:
"scripts": { "dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline", "build": "webpack --progress --colors" }
7. inline
webpack-dev-server 有兩種模式可以實現自動重新整理和模組熱替換機制。
1. iframe
頁面是被嵌入到一個iframe頁面,並且在模組變化的時候過載頁面。
可能如上解釋,我們還不能完全能理解到底是什麼意思,沒有關係,我們繼續來看下配置和實踐效果。
module.exports = { devServer: { port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, inline: false }, }
如上程式碼配置 inline: false 就是使用iframe模式來過載頁面了。我們的目錄結構還是上面的那種結構,然後我們只需要在webpack中所有
配置如下:
const path = require('path'); // 提取css的外掛 const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: './js/main.js', output: { filename: 'bundle.js', // 將輸出的檔案都放在dist目錄下 path: path.resolve(__dirname, 'dist'), publicPath: '/dist' }, mode: 'development', module: { rules: [ { // 使用正則去匹配要用該loader轉換的css檔案 test: /\.css$/, loaders: ExtractTextPlugin.extract({ // 轉換 .css檔案需要使用的Loader use: ['css-loader'] }) }, { test: /\.(png|jpg)$/, loader: 'url-loader', options: { limit: 10000, name: '[name].[ext]' } } ] }, resolve: { // modules: ['plugin', 'js'] }, devServer: { port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, inline: false }, plugins: [ new ExtractTextPlugin({ // 從js檔案中提取出來的 .css檔案的名稱 filename: `main.css` }) ] };
然後當我們在命令列中,輸入 webpack-dev-server 後 回車,可以看到如下圖所示:
接著我們在瀏覽器下 輸入 http://0.0.0.0:8081/webpack-dev-server/ 地址後 回車,即可看到頁面,我們檢視原始碼的時候,會看到嵌入了一個iframe頁面,如下圖所示:
當我們重新修改main.js 或 它的依賴檔案 demo1.js 的時候,儲存後,它也會自動重新載入頁面,這就是使用 iframe 模式來配置載入頁面的。
iframe 模式的特點有:
1. 在網頁中嵌入了一個iframe,將我們自己的應用程式碼注入到 這個 iframe中去了。
2. 在頁面頭部會有一個 App ready. 這個提示,用於顯示構建過程的狀態資訊。
3. 載入了 live.bundle.js檔案,還同時包含了 socket.io的client程式碼,進行了 websocket通訊,從而完成了自動編譯打包,頁面自動重新整理功能。
我們看下請求的所有檔案有如下:
2. inline 模式
開啟模式,只需要把上面的配置程式碼變為 inline: true即可,它在構建變化後的程式碼會通過代理客戶端來控制網頁重新整理。
如上配置後,我們執行 webpack-dev-server 命令後,如下所示:
接著我們在位址列中 http://0.0.0.0:8081/ 執行下 就可以訪問到 專案中的根目錄 index.html了,當我們修改入口檔案的程式碼儲存也一樣
能實時重新整理,其實效果是一樣的。
inline模式的特點有:
1. 構建的訊息在控制檯中直接顯示出來。
2. socket.io的client程式碼被打包進bundle.js當中,這樣就能和websocket通訊,從而完成自動編譯工作,頁面就能實現自動重新整理功能。
3. 以後的每一個入口檔案都會插入上面的socket的一段程式碼,這樣會使的打包後的bundle.js檔案變得臃腫。
8. open
該屬性用於DevServer啟動且第一次構建完成時,自動使用我們的系統預設瀏覽器去開啟網頁。
如下配置:
module.exports = { devServer: { // contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, // hot: true, inline: true, open: true } }
設定 open: true 即可,當我們執行完成 npm run dev 打包的時候,會自動開啟預設的瀏覽器來檢視網頁。
9. overlay
該屬性是用來在編譯出錯的時候,在瀏覽器頁面上顯示錯誤。該屬性值預設為false,需要的話,設定該引數為true。
為了演示下,我們來在main.js 程式碼內使用ES6的語法來編寫程式碼,ES6是使用babel-loader 這樣的來轉化的,但是目前我們的專案先不安裝該loader,應該會報錯的。比如在main.js 程式碼加如下一句程式碼:
const a;
配置 overlay: true即可:如下配置:
module.exports = { devServer: { // contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, // hot: true, inline: true, open: true, overlay: true } }
執行 npm run dev 後,自動開啟網頁,顯示如下所示:
10. stats(字串)
該屬性配置是用來在編譯的時候再命令列中輸出的內容,我們沒有設定 stats的時候,輸出是如下的樣子:如下所示:
該屬性值可以有如下值:
stats: 'errors-only' 表示只列印錯誤,我們新增下這個配置到devServer中;如下程式碼配置:
module.exports = { devServer: { // contentBase: path.join(__dirname, "dist"), port: 8081, host: '0.0.0.0', headers: { 'X-foo': '112233' }, // hot: true, inline: true, open: true, overlay: true, stats: 'errors-only' } }
現在我們繼續 執行 npm run dev 後,會看到命令列中顯示如下:
該配置的含義是 只有錯誤的才會被列印,沒有錯誤就不列印,因此多餘的資訊就不會顯示出來了。
該屬性值還有 'minimal', 'normal', 'verbose' 等。
11. compress
該屬性是一個布林型的值,預設為false,當他為true的時候,它會對所有伺服器資源採用gzip進行壓縮。
12. proxy 實現跨域
有時候我們使用webpack在本地啟動伺服器的時候,由於我們使用的訪問的域名是 http://localhost:8081 這樣的,但是我們服務端的介面是其他的,
那麼就存在域名或埠號跨域的情況下,但是很幸運的是 devServer有一個叫proxy配置項,可以通過該配置來解決跨域的問題,那是因為 dev-server 使用了 http-proxy-middleware 包(瞭解該包的更多用法 )。
假如現在我們本地訪問的域名是 http://localhost:8081, 但是我現在呼叫的是百度頁面中的一個介面,該介面地址是:http://news.baidu.com/widget?ajax=json&id=ad。現在我們只需要在devServer中的proxy的配置就可以了:
如下配置:
proxy: { '/api': { target: 'http://news.baidu.com', // 目標介面的域名 // secure: true, // https 的時候 使用該引數 changeOrigin: true, // 是否跨域 pathRewrite: { '^/api' : '' // 重寫路徑 } } }
因此所有的配置如下:
module.exports = { devServer: { // contentBase: path.join(__dirname, "dist"), headers: { 'X-foo': '112233' }, // hot: true, port: '8081', inline: true, open: true, overlay: true, stats: 'errors-only', proxy: { '/api': { target: 'http://news.baidu.com', // 目標介面的域名 // secure: true, // https 的時候 使用該引數 changeOrigin: true, // 是否跨域 pathRewrite: { '^/api' : '' // 重寫路徑 } } } } }
然後我們在main.js裡面編寫如下程式碼:
import axios from 'axios'; axios.get('/api/widget?ajax=json&id=ad').then(res => { console.log(res); });
在這裡請求我使用 axios 外掛,其實和jquery是一個意思的。為了方便就用了這個。
下面我們來理解下上面配置的含義:
1. 首先是百度的介面地址是這樣的:http://news.baidu.com/widget?ajax=json&id=ad;
2. proxy 的配置項 '/api' 和 target: 'http://news.baidu.com' 的含義是,匹配請求中 /api 含有這樣的域名 重定向 到 'http://news.baidu.com'來。因此我在介面地址上 新增了字首 '/api', 如: axios.get('/api/widget?ajax=json&id=ad'); 因此會自動補充字首,也就是說,url: '/api/widget?ajax=json&id=ad' 等價
於 url: 'http://news.baidu.com/api/widget?ajax=json&id=ad'.
3. changeOrigin: true/false 還引數值是一個布林值,含義是 是否需要跨域。
4. secure: true, 如果是https請求就需要改引數配置,需要ssl證書吧。
5. pathRewrite: {'^/api' : ''}的含義是重寫url地址,把url的地址裡面含有 '/api' 這樣的 替換成 '',
因此介面地址就變成了 http://news.baidu.com/widget?ajax=json&id=ad; 因此就可以請求得到了,最後就返回
介面資料了。
如下圖所示: