深入淺出的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; 因此就可以請求得到了,最后就返回
接口數據了。
如下圖所示: