摘要:一看就懂之基礎配置一簡介本質上,是一個現代應用程序的靜態模塊打包器。屬性表示的是的上下文目錄,配置入口文件的時候,如果入口文件使用的是相對路徑,那么就是相對于所在的目錄。通常用于指定以何種方式導出庫,通常用于指定接收庫的名稱。
一看就懂之webpack基礎配置 一、webpack 簡介
本質上,webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關系圖(dependency graph),其中包含應用程序需要的每個模塊,然后將所有這些模塊打包成一個或多個 bundle。
簡單說,webpack可以看做是一個模塊打包機,主要作用就是: 分析你的項目結構,找到JavaScript模塊以及一些瀏覽器不能直接運行的拓展語言(sass、less、typescript等),然后將它們打包為合適的格式以供瀏覽器使用。
webpack主要實現的功能:
代碼轉換(如: ES6轉換ES5、sass和less轉換為css等)二、webpack 安裝
文件優化(如: 將模塊內容進行壓縮)
代碼分割(如: 多頁面應用公共模塊的抽離、路由懶加載)
模塊合并(如: 按照不同的功能將多個模塊合并為一個模塊)
自動刷新(如: 啟動本地服務,代碼更新后進行自動刷新)
代碼校驗(如: 添加eslint進行代碼規范檢查)
自動發布(如: 應用打包完成后,自動發布)
在webpack 3中,webpack本身和它的CLI以前都是在同一個包中的,但在第4版之后,已經將兩者分開來更好地管理它們,所以安裝webpack4之后的版本,要同時安裝webpack和webpack-cli
注意,安裝webpack的時候,必須進行本地安裝才能生效,否則會報錯,如:
但是全局安裝了也有一個好處,那就是可以在項目根目錄下直接執行webpack即可完成項目的打包,如果沒有進行全局安裝,那么可以通過npx直接執行項目本地安裝的模塊,即 npx webpack也可以完成項目的打包。三、webpack 基礎配置
webpack是支持零配置的,即不需要配置文件即可完成打包,其默認入口文件為項目根目錄下的src目錄下的index.js文件,其默認出口為項目根目錄下的dist目錄的main.js
如果沒有給webpack添加配置文件,那么webpack的打包能力就會非常弱,webpack執行的時候默認會加載項目根目錄下的webpack.config.js文件,注意,該配置文件是一個js文件,而不是json文件,并且其是通過node去執行的,所以其完全支持node語法,即node中能用的,在配置文件中都可以用
webpack配置文件必須要對外暴露一個對象,即通過module.exports進行對外暴露,其中的所有配置都必須寫在這個對外暴露的對象中。
① context
context屬性表示的是webpack的上下文目錄,配置入口文件的時候,如果入口文件使用的是相對路徑,那么就是相對于context所在的目錄。
context默認值為執行webpack命令時所在的當前工作目錄,通常是在項目根目錄下執行webpack命令,所以可以認為其值默認為項目根目錄,所以如果入口文件路徑寫成相對路徑,最好將context配置成context: path.resolve(__dirname),以防止在非項目根目錄下執行webpack命令時找不到入口文件路徑而報錯。
② entry
entry用于配置模塊的入口文件,可以配置多個,webpack將從入口文件開始搜索以及遞歸解析所有入口文件依賴的模塊,其是必填的,如果配置文件中沒有entry則會報錯。entry的屬性值可以是表示路徑的單個字符串,也可以是數組,數組中的元素為入口文件的路徑,還可以是對象,對象的屬性名為入口文件的chunk名,即打包后輸出文件的名字,屬性值為入口文件的路徑。注意,入口文件的路徑可以是絕對路徑,也可以是相對路徑,相對路徑默認是以配置文件中的context屬性值表示的路徑
module.exports = { entry: "./foo.js" // 屬性值為一個表示路徑的字符串 }
其輸出結果文件取決于配置文件的output配置,如果output.filename沒有配置,則默認輸出為main.js,如果output.filename值為指定的名稱,則輸出結果為output.filename的屬性值
module.exports = { entry: [ "./foo.js", "./bar.js"] // 屬性值為一個數組 }
其輸出結果文件也是取決于配置文件的output配置,只不過,其會將foo.js和bar.js一起打包輸出為一個文件,如果output.filename沒有配置,則默認輸出為main.js,如果output.filename值為指定的名稱,則輸出結果為output.filename的屬性值
module.exports = { entry: { // 屬性值為一個對象 a: "./src/bar.js", b: "./src/foo.js", c: "./src/index.js" } }
其輸出結果不再取決于output.filename的配置,因為entry已經指定好了模塊輸出的chunk名,即會分別輸出a.js、b.js和c.js三個文件,并且此時output.filename屬性值不能配置為一個固定名稱的輸出文件,因為入口文件有多個,必然輸出文件也會有多個
chunk和module的區別,二者都是表示模塊,但是module可以看做是具有獨立功能的小模塊,即小塊,也就是打包之前,程序員編寫的一個一個的文件,每個文件稱為一個module;而chunk則可以看做是由多個小module打包成的大模塊,即大塊
③ output
output配置的是如何輸出最終想要的代碼,output是一個object。
path: 用于配置打包后輸出文件的本地存放目錄,必須是絕對路徑,當然也可以不配置,因為如果沒有配置path,那么會自動在執行webpack命令時所在的目錄下自動創建dist目錄并將打包結果輸出到dist目錄下,與context的配置路徑無關
module.exports = { output: { path: path.resolve(__dirname, "./dist") // 必須是絕對路徑 } }
filename: 用于配置輸出文件的名稱,如果只有一個輸出文件,那么可以配置成靜態不變的文件名,如:
module.exports = { output: { filename: "bundle.js" } }
但是,如果有多個chunk要輸出時,即入口文件配置了多個時,那么filename就不能配置成靜態不變的了,就必須借助模板和變量了,常見的兩個變量,如:
[name]: 可以自動獲取到入口文件配置的chunk名稱;
[hash]: 可以自動生成hash值,hash值的長度是可以指定的,默認為20位;
module.exports = { output: { filename: "[name][hash:8].js" // 以入口文件設置的chunk作為輸出名,并且指定hash值為8位 } }
library和libraryTarget: 用于指定將模塊的輸出結果掛載到哪個地方或者以什么樣的方式導出庫(模塊輸出結果)。二者通常要搭配一起使用。
libraryTarget通常用于指定以何種方式導出庫,library通常用于指定接收庫的名稱。
我們將入口的一個或多個js文件打包輸出后的結果也是一個js文件,在沒有配置library和libraryTarget的時候,這個輸出的js文件中包含了一個匿名自執行函數, 即輸出文件的執行結果沒有被任何東西接收,我們引入輸出文件執行后不會得到任何結果。 如:
(function(){ console.log("foo"); return "foo"; // 雖然有返回值,但是匿名自執行函數執行完畢后拿不到任何結果 })(); // 我們以var 變量 的方式來接收函數的返回值 var foo = (function(){ // 匿名自執行函數執行完畢后就會將函數返回值保存到foo變量上 console.log("foo"); return "foo"; })(); console.log(`foo is ${foo}`);
打包后的輸出文件的輸出結果(導出結果),就是入口文件的輸出結果(導出結果),即入口文件通過export、exports、module.exports等輸出(導出)的結果
var: 將libraryTarget設置為var, 同時指定一個自定義的變量名來接收模塊的輸出,這個自定義的變量名就是library的屬性值
module.exports = { output: { filename: "bundle.js", path: path.resolve(__dirname, "./dist/"), libraryTarget: "var", library: "test" } }
模塊的輸出結果將會賦值給test變量,其輸出文件bundle.js內容大致如下:
var test = (function(modules){ return result; // 返回值result將會被賦值給test變量 })();
commonjs: 將libraryTarget設置為commonjs, 即通過commonjs規范導出,同時指定一個自定義的變量名來接收模塊的輸出,這個自定義的變量名就是library的屬性值, 只不過這個自定義的變量名是exports的屬性名,如:
module.exports = { output: { filename: "bundle.js", path: path.resolve(__dirname, "./dist/"), libraryTarget: "commonjs", library: "test" } }
模塊的輸出結果將會賦值給exports["test"]上,其輸出文件bundle.js內容大致如下:
exports["test"] = (function(modules){ return result; // 返回值result將會被賦值給exports["test"] })();
commonjs2: 將libraryTarget設置為commonjs2,即通過commonjs2規范導出,此時library的配置將無意義,因為commonjs2的輸出是固定的module.exports,所以不需要指定library了,如:
module.exports = { output: { filename: "bundle.js", path: path.resolve(__dirname, "./dist/"), libraryTarget: "commonjs2" } }
模塊的輸出結果將會被賦值到module.exports上,其輸出文件bundle.js內容大致如下:
module.exports = (function(modules){ return result; // 返回值result將會被賦值給module.exports })();
commonjs和commonjs2的區別在于,commonjs只能使用exports進行導出,而commonjs2在commonjs的基礎上增加了module.exports進行導出;
this: 將libraryTarget設置為this, 那么此時library配置的變量名將作為this的屬性名來接收模塊的導出結果,如:
module.exports = { output: { filename: "bundle.js", path: path.resolve(__dirname, "./dist/"), libraryTarget: "this", library: "test" } }
模塊的輸出結果將會被賦值到this["test"] 上,其輸出文件bundle.js內容大致如下:
this["test"] = (function(modules){ return result; // 返回值result將會被賦值給this["test"] })();
同理libraryTarget的屬性值還可以是window、global,這里就不一一列舉了。
publicPath
publicPath用于配置打包資源發布到線上時服務器的url地址,打包完成后,html文件中如果引入了js、image、css等資源,那么都會在前面加上publicPath所表示的路徑
module.exports = { output: { filename: "bundle.js", path: path.resolve(__dirname, "./dist/"), publicPath: "http://www.lihb.com/" } }
// index.html
四、webpack 打包輸出后的內容分析
webpack打包輸出后的結果默認是一個匿名自執行函數,匿名自執行函數傳遞的參數為一個對象,對象的屬性名為入口文件的路徑名,屬性值為一個函數,函數體內部通過會執行eval(),eval()方法的參數為入口文件的內容字符串,而這個匿名自執行函數,內部有一個自定義的__webpack_require__方法,該方法需要傳入入口文件的路徑名作為參數,匿名自執行函數執行完成后會返回__webpack_require__的結果,而__webpack_require__()方法內部執行的時候,會首先創建一個module對象,module對象里面有exports屬性,屬性值為一個空的對象,用于接收入口文件的模塊輸出,如:
(function(modules) { function __webpack_require__(moduleId) { // 傳入入口文件的路徑 var module = installedModules[moduleId] = { // 創建一個module對象 i: moduleId, l: false, exports: {} // exports對象用于保存入口文件的導出結果 }; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 執行入口文件 return module.exports; // 返回模塊輸出結果 } return __webpack_require__(__webpack_require__.s = "./src/bar.js"); // 返回入口文件 })({ "./src/bar.js": (function(module, exports) { eval("module.exports = "bar";"); }) });
所以不管入口文件是以ES6模塊的方式輸出還是以commonjs模塊的方式輸出,最終入口文件的模塊輸出結果都會被綁定到__webpack_require__方法中定義的module對象的exports屬性上,只不過,如果是以commonjs的方式輸出,那么入口文件的輸出結果將會直接替換掉__webpack_require__方法中定義的module對象的exports屬性;如果是以ES6模塊的方式輸出,則是在__webpack_require__方法中定義的module對象的exports屬性值中添加一個default屬性或者具體變量名來保存入口文件的輸出。五、webpack 本地服務器配置
為了更方便調試,我們需要用到webpack的本地http服務器功能,要想使用webpack提供的Web服務器功能,我們需要安裝webpack-dev-server模塊,webpack-dev-server會啟動一個web服務器用于實現網頁請求,也可以監聽文件的變化自動刷新網頁。
webpack-dev-server模塊安裝完成后,我們可以在項目根目錄下運行npx webpack-dev-server,其和webpack命令一樣,如果沒有配置文件,則使用內置默認配置進行打包輸出,如果有則使用配置文件中的配置,只不過其不會將打包結果輸出到指定的目錄中,因為webpack-dev-server會忽略配置文件中的output.path配置,其會將打包輸出結果保存到內存中。webpack-dev-server啟動后會默認將啟動devServer時所在的目錄作為根目錄,即執行npx webpack-dev-server命令時所在的目錄。
webpack提供了一個devServer屬性用于配置啟動的服務器的一些參數,當然webpack本身是無法識別devServer屬性配置的,只有通過webpack-dev-server去啟動webpack時,devServer的配置才會生效。
module.exports = { devServer: { port: 3000, // 讓devServer監聽3000端口 contentBase: "./dist", // 將當前項目的dist目錄作為devServer的根目錄 progress: true, // 顯示打包進度條 compress: true // 是否啟用Gzip壓縮,默認為false } }
webpackDevServer啟動后,默認會自動監聽打包源文件的變化,如果修改了打包源文件,那么會自動重新打包到內存,并且會自動刷新瀏覽器,但是自動刷新瀏覽器功能必須將target設置成web,否則自動刷新功能將會失效,比如target為node就無法起作用。六、webpack 插件配置
在不使用插件的時候,webpack默認只能打包輸出js文件,如果我們想要輸出其他格式的文件到輸出目錄中,那么我們必須使用插件。webpack提供了一個plugins屬性用于配置使用到的插件,其屬性值為一個數組,數組中的元素為插件對象,通常插件都是一個類,我們需要通過插件類來創建一個插件對象。
① html-webpack-plugin
該插件可以指定一個html文件,webpack會將該html文件打包輸出到輸出目錄中,同時會將打包輸出后的文件自動插入到該html文件中,即讓該html文件自動引入打包后的js文件。
module.exports = { plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", // 要打包輸出哪個文件,可以使用相對路徑 filename: "index.html", // 打包輸出后該html文件的名稱 minify: { removeAttributeQuotes: true, // 去除html文件中的引號 collapseWhitespace: true // 合并空格,即將html進行單行顯示 }, hash: true // 向html文件中引入打包后的js時候帶上hash值 }) ] }
html插件中配置了hash為true, 是在引入打包后的js的時候帶上hash值,如:
webpack默認將所有格式的文件都當做模塊進行處理,但是wepback默認只能處理js模塊。如果在js中通過require引入了其他格式的模塊(文件),那么webpack就必須通過安裝合適的模塊加載器,才能正確解析對應的模塊內容,webpack提供了一個module屬性,用于進行模塊解析器的配置,其屬性值為一個對象,對象中有一個rules屬性,其屬性值為一個數組,數組中的元素為一個對象,該對象主要完成兩件事,匹配對應格式的文件,并且使用對應模塊加載器進行加載,匹配使用的是test屬性,屬性值為一個正則表達式,【使用】使用的是use屬性,屬性值可以是字符串也可以是數組,如果只有一個模塊加載器的時候,可以使用字符串的形式,如果有多個模塊加載器的時候,那么就需要使用數組的形式,當然,如果模塊加載器需要傳遞參數配置,那么可以將模塊加載器寫成對象的形式,通過loader屬性指定模塊加載器名稱,通過options屬性傳遞參數配置。
① 處理css樣式,需要使用到css-loader和style-loader。
首先需要安裝css-loader和style-loader。
css-loader必須同時和style-loader一起使用才能正確加載css文件,一個負責加載,一個負責插入。css-loader負責加載css, 即在js文件中能夠通過require的方式引入css,即加載和解析css,同時支持在css文件中使用@ import的方式引入其他css文件,style-loader負責將加載并解析好的css文件插入到html文件中去,從名字可以看出其是在html文件中生成style標簽來引入css文件,loader的執行順序是從右向左,所以必須先加載然后再插入
比如,打包入口文件index.js中通過require的方式引入了一個index.js文件,即require("./index.css"),那么webpack需要進行如下配置:
module.exports = { module: { rules: [ { test: /.css$/, // 匹配以.css結尾的文件 use: [ // 并交給css-loader和style-loader進行處理 { loader: "style-loader", // 以對象的形式配置loader options: { // 通過options給loader傳遞參數 insertAt: "top" // 默認為bottom, 將加載的css文件插入到head標簽的最上面,即優先級最低,會被覆蓋 } }, "css-loader" // 直接以字符串的形式配置loader ] } ] } }
打包輸出后,會將index.css中的內容放到