webpack最佳入門實踐系列(09)
9.路徑相關
原來我們打包的東西都存放到了dist目錄下,並沒有進行分類存儲,亂成一團,這一節我們就要處理一下打包的路徑,讓打包後的目錄看起來更加優雅
9.1.代碼準備
我們先建立起這樣一個目錄結構
. ├── node_modules ├── src | ├── assets | └── css | └── index.css | └── img | └── noding.jpg | └── js | └── index.js | └── index.html ├── .babelrc ├── package-lock.json ├── package.json ├── webpack.config.js └── dist
你只需要把前面我們寫的代碼copy一份,然後刪除掉dist裏面的東西並且在src下新建assets文件夾來裝css和img就可以了
我們在src/index.html中輸入一段代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="box"></div> <i class="fa fa-bath" aria-hidden="true"></i> <i class="fa fa-envelope-open" aria-hidden="true"></i> <i class="fa fa-microchip" aria-hidden="true"></i> <i class="fa fa-user-circle-o" aria-hidden="true"></i> </body> </html>
接下來在assets/js/index.js中輸入下面代碼
// 引入icon圖標字體 import "font-awesome/css/font-awesome.css" // 引入圖片 import imgSrc from ‘../img/nodeing.jpg‘ // 把圖片插入到html網頁中 document.getElementById("box").innerHTML = ‘<img src="‘+imgSrc+‘" />‘
然後去webpack.config.js中去修改一下入口文件位置
接下來,我們去測試一下打包效果
npm run dev
所有文件都打包到dist目錄下,到此,我們這小節準備工作已經完成,接下來,我們需要去優化webpack配置,讓打包出來的文件不那麽混亂
9.2.webpack配置
9.2.1.把js文件分類
我們希望打包出來的文件也像src目錄一樣能夠分門別類的存放,首先,我們把js單獨放進assets/js文件夾下,這時我們需要修改webpack配置文件,把輸出目錄改掉
output: { path: path.resolve(__dirname, ‘dist/assets‘), filename: ‘js/app.js‘ },
運行npm run dev 查看效果,發現dist目錄下多出了一個assets文件,並且已經建立好了js文件夾,app.js也被放進來了,但是,原來打包出來的那些文件還在,這時候,dist目錄就更亂了,我們希望每次打包生成的文件都是最新的,得手動去刪除上次打包出來的文件,像這種手動刪除dist目錄的操作,我們可以交給webpack相關插件來完成,這個插件叫clean-webpack-plugin
安裝clean-webpack-plugin插件
npm install clean-webpack-plugin --save
修改webpack配置文件,引入clean-webpack-plugin插件
const path = require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") // 引入clean-webpack-plugin插件 const CleanWebpackPlugin = require("clean-webpack-plugin") module.exports = { entry: "./src/assets/js/index.js", output: { path: path.resolve(__dirname, ‘dist/assets‘), filename: ‘js/app.js‘ }, plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html‘, filename: ‘index.html‘ }), // 使用插件,設置需要清除的目錄 new CleanWebpackPlugin([‘dist‘]) ], devServer: { open: true }, module: { rules: [ { test: /\.js$/, use:[‘babel-loader‘], exclude: path.resolve(__dirname, ‘node_modules‘) }, { test: /\.css$/, use: [‘style-loader‘, ‘css-loader‘] }, // 處理文字 { test: /\.(eot|svg|ttf|woff|woff2)$/, use: ‘file-loader‘ }, { test: /\.(jpg|png|gif|webp|bmp)$/, use: [{ loader: ‘url-loader‘, options: { limit: 10240 } }] } ] } }
這個時候你會發現,每次運行npm run dev,這個插件就會先把上次的dist目錄刪除,然後再新建一個dist目錄,把新打包的文件放裏面
以上步驟,我們把js文件放到了assets/js目錄下,但隨之而來的就是,所有的文件都被打包到assets這個目錄下了,我們不希望index.html這個文件也被打包進assets目錄下,而是像在src目錄下一樣,它們應該是同級的,所以我們需要修改一下 index.html被輸出的路徑
plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html‘, // 這裏表示往外回退一層 filename: ‘../index.html‘ }), new CleanWebpackPlugin([‘dist‘]) ],
9.2.2.把字體文件分類
修改webpack配置文件,給字體文件增加輸出規則
// 處理文字 { test: /\.(eot|svg|ttf|woff|woff2)$/, use: [{ loader: "file-loader", options: { name: ‘fonts/[name]_[hash:4].[ext]‘ } }] },
其中,[name]、[hash]、[ext]都是可變的,name表示原來的文件名字,hash表示生成的hash值,hash:4表示可以限定hash字符串的位數,ext表示原來文件的擴展名(文件後綴)
9.2.3.把圖片文件分類
修改webpack配置文件,給圖片文件增加輸出規則
{ test: /\.(jpg|png|gif|webp|bmp)$/, use: [{ loader: ‘url-loader‘, options: { limit: 10240, name: ‘img/[name]_[hash:4].[ext]‘ } }] }
因為,ulr-loader是對file-loader的封裝,其中的[name]、[hash]、[ext]表示的意思和file-loader中表示的意思是一樣的
9.2.4.把css文件分類
css文件是打包進js文件的,如果需要單獨打包出來,需要安裝另一個插件,extract-text-webpack-plugin
npm install extract-text-webpack-plugin --save-dev
這個插件的使用規則和其他plugin差不多,首先需要引入插件
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin")
其次,是需要實例化對象,並且傳入配置項,然後將實例化出來的這個對象加入到plugins選項中
const extractcss = new ExtractTextWebpackPlugin({ //打包輸出的路徑 filename: ‘assets/css/index.css‘ }) plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html‘, filename: ‘./index.html‘ }), new CleanWebpackPlugin([‘dist‘]), // 這裏是前面實例化得到的對象 extractcss ],
最後,需要修改css輸出規則
{ test: /\.css$/, use: extractcss.extract({ fallback:‘style-loader‘, use:[‘css-loader‘] }) },
9.2.5.修正url地址
當我們把各種類型的文件分類好以後,運行打包出來的index.html,發現圖片、字體、css等的路徑是不正確的,沒辦法正常工作,這個時候,我們需要用output配置項下的 publicPath來修正一下
output: { path: path.resolve(__dirname, ‘dist/assets‘), filename: ‘js/app.js‘, publicPath: ‘assets/‘ },
其中,publicPath設置的是所有靜態資源的基礎目錄,要快速理解它,可以記住下面的公式
靜態資源最終訪問路徑 = output.publicPath + 資源loader或插件等配置路徑
舉例說明,我們在各種loader中設置的路徑如下:
// css輸出路徑 name: ‘css/[name]_[hash:4].[ext]‘ // 圖片輸出路徑 name: ‘img/[name]_[hash:4].[ext]‘ // 字體文件輸出路徑 name: ‘fonts/[name]_[hash:4].[ext]‘ // js文件輸出路徑 filename: ‘js/app.js‘
設置的publicPath為 "assets/", 那麽最終的訪問路徑就是 publicPath/loader設置的路徑,例如圖片路徑就會是 assets/img/[name]_[hash:4].[ext]
總結來說就是最終的url訪問路徑是拿publicPath+loader路徑相拼接的
接下來,我們npm start啟動我們的項目,發現最終訪問的是項目的根目錄,而並沒有去訪問打包出來的那個index.html文件,在找出沒有訪問到index.html文件的原因之前,我們先來講清楚webpack中幾個非常容易混淆的路徑問題
1.output.path
2.output.publicPath
3.devServer.publicPath
4.devServer.contentBase
output.path 是靜態資源的輸出目錄,打包後的資源都放到這裏
output.publicPath是靜態資源的基礎目錄,設置這個目錄後,最終的靜態資源訪問路徑前面都會拼接上這個選項設置的值,這也方便我們單獨設置靜態資源服務器地址,例如:靜態資源服務器地址是http://static.nodeing.com, 設置了這個值,最終訪問圖片的地址就會是 http://static.nodeing.com/assets/img/nodeing.com 這種形式,因此,這也是output.publicPath這個選項和後面devServer.publicPath比較大的區別,前者是可以設置http網址的
devServer.publicPath這個選項是webpack-dev-server服務器的訪問路徑,我們知道當webpack-dev-server啟動後,會把資源打包到內存,你不需要關註它在內存中的什麽地方,你可以理解為打包後的資源也被輸出了,只是輸出到內存中,你無法在硬盤上看見而已,但是它提供了一個地址來供我們訪問,devServer.publicPath就是用來設置訪問內存中被打包出來的資源地址的,舉例說明:
output: { path: path.resolve(__dirname, ‘dist/assets‘), filename: ‘js/app.js‘, }, devServer: { open: true, publicPath: ‘/aa‘ }, 其他配置省略...
像上面這種配置,當服務器啟動的時候,如果想訪問到內存中的app.js,那麽需要通過http://localhost:8080/aa/js/app.js
回到開始的問題,npm start啟動服務器後,無法訪問到打包出來的index.html? 我們來找找具體的原因,說明一下幾種路徑的問題
當前的完整的webpack配置是這樣的:
const path = require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") const CleanWebpackPlugin = require("clean-webpack-plugin") module.exports = { entry: "./src/assets/js/index.js", output: { path: path.resolve(__dirname, ‘dist/assets‘), filename: ‘js/app.js‘, publicPath: ‘assets/‘ }, plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html‘, filename: ‘../index.html‘ }), new CleanWebpackPlugin([‘dist‘]) ], devServer: { open: true, containtBase: ‘src/‘ publicPath: ‘/aa‘ }, module: { rules: [ { test: /\.js$/, use:[‘babel-loader‘], exclude: path.resolve(__dirname, ‘node_modules‘) }, { test: /\.css$/, use: [ ‘style-loader‘, { loader: ‘css-loader‘, options: { name: ‘css/[name]_[hash:4].[ext]‘ } } ] }, // 處理文字 { test: /\.(eot|svg|ttf|woff|woff2)$/, use: [{ loader: "file-loader", options: { name: ‘fonts/[name]_[hash:4].[ext]‘ } }] }, { test: /\.(jpg|png|gif|webp|bmp)$/, use: [{ loader: ‘url-loader‘, options: { limit: 10240, name: ‘img/[name].[ext]‘ } }] } ] } }
我們來解讀幾條關鍵的配置:
1.path: path.resolve(__dirname, ‘dist/assets‘), 這條配置是打包後資源輸出的目錄,同樣的當webpack-dev-server啟動後,資源會打包到內存,用不太嚴謹但通俗的話來說你可以認為所有文件被打包到這個目錄,只是這個目錄放在內存而不是輸出到硬盤,你訪問這個目錄裏面的文件的時候,路徑和打包到硬盤是看到的路徑一樣,例如:在硬盤上 dist/assets/js/app.js, 在內存中也通過這種目錄結構訪問就是了
2.publicPath: ‘assets/‘,這個配置,前面我們也講到了,是靜態資源的基礎路徑,可以返回去再閱讀一下
3.publicPath: ‘/aa‘, 這個配置是服務器訪問內存的虛擬路徑, http://localhost:8080/aa 這個地址,你可以認為是映射到了output.path設置的目錄,不太嚴謹但通俗的說,你可以認為http://localhost:8080/aa 這個地址指向了這個目錄(dist/assets/),那麽你在http://localhost:8080/aa後面加 /js/app.js 就相當於訪問 dist/assets/js/app.js,所以啟動以後通過http://localhost:8080/aa/js/app.js是可以訪問到這個js文件的
4.filename: ‘../index.html‘,這個在HtmlWepackPlugin中的配置,設置了被打包出來的html位置,../表示往上一層返回,那就意味著,最終打包出來的結構是這樣的:
├── dist | ├── assets | ├── css | ├── img | ├── js | ├── index.html |
我們通過http://localhost:8080/aa 能訪問到dist/assets目錄,也可以訪問該目錄下層級更深的文件,只需要在http://localhost:8080/aa 後面加文件路徑就可以了,但是卻永遠無法返回到上層目錄去訪問到 index.html
當訪問不到內存中的index.html文件,webpack-dev-server就會去訪問本地的路徑,這個本地的路徑就是containtBase: ‘src/‘這個選項設置的,所以npm start的時候,直接打開的是項目下的src目錄
好了,問題原因已經找出來了,接下來我們需要修改以下webpack配置,具體的思路就是
1. 輸出目錄往上挪動一層 output.path 設置成 path.resolve(__dirname, ‘dist‘),
2. 在每個loader上加深一層 js加深一層是這樣的 assets/js/app.js 圖片加深一層是這樣的 assets/img/...,其他,同理
3. ../index.html 改為當前目錄 ./index.html
4. output.publicPath的值設置來和devServer.publickPath的值一樣,不然靜態資源的訪問地址會出錯,返回404
這樣做,原來打包輸出的目錄變成了dist目錄,而不是原來的dist/assets目錄, 那麽http://localhost:8080/aa 這個地址指向的內存虛擬目錄就是 dist目錄,那麽通過http://localhost:8080/aa/index.html 就可以訪問到內容了
具體完整配置如下:
const path = require("path") const HtmlWebpackPlugin = require("html-webpack-plugin") const CleanWebpackPlugin = require("clean-webpack-plugin") module.exports = { entry: "./src/assets/js/index.js", output: { path: path.resolve(__dirname, ‘dist‘), filename: ‘assets/js/app.js‘, publicPath: ‘/aa/‘ }, plugins: [ new HtmlWebpackPlugin({ template: ‘./src/index.html‘, filename: ‘./index.html‘ }), new CleanWebpackPlugin([‘dist‘]) ], devServer: { open: true, publicPath: ‘/aa‘ }, module: { rules: [ { test: /\.js$/, use:[‘babel-loader‘], exclude: path.resolve(__dirname, ‘node_modules‘) }, { test: /\.css$/, use: [ ‘style-loader‘, { loader: ‘css-loader‘, options: { name: ‘assets/css/[name]_[hash:4].[ext]‘ } } ] }, // 處理文字 { test: /\.(eot|svg|ttf|woff|woff2)$/, use: [{ loader: "file-loader", options: { name: ‘assets/fonts/[name]_[hash:4].[ext]‘ } }] }, { test: /\.(jpg|png|gif|webp|bmp)$/, use: [{ loader: ‘url-loader‘, options: { limit: 10240, name: ‘assets/img/[name].[ext]‘ } }] } ] } }
通常output.publicPath和devServer.publicPath設置成 "/",這樣我們就可以通過 http://localhost:8080/ 來訪問打包出來的index文件了,自己動手改改,試試效果吧!!
webpack最佳入門實踐系列(09)