webpack的基本使用2
1、webpack中的module、chunk和bundle的區別
webpack中的module、chunk和bundle的區別:
- 對于一份同邏輯的代碼,當我們手寫下一個一個的文件,它們無論是 ESM 還是 commonJS 或是 AMD,他們都是 module ;
- 當我們寫的 module 源文件用 webpack 進行打包時,webpack 會根據文件引用關系生成 chunk 文件,webpack 會對這個 chunk 文件進行一些操作;
- webpack 處理好 chunk 文件后,最后會輸出 bundle 文件,這個 bundle 文件包含了經過加載和編譯的最終源文件,所以它可以直接在瀏覽器中運行。

module、chunk 和 bundle 其實就是同一份邏輯代碼在不同轉換場景下的取了三個名字,我們直接寫出來的是 module,webpack 處理時是 chunk,最后生成瀏覽器可以直接運行的 bundle。
一般來說一個 chunk 對應一個 bundle,比如上圖中的 utils.js -> chunks 1 -> utils.bundle.js;但我們也可以用一些插件進行文件分離,比如說上圖中,就用 MiniCssExtractPlugin 插件從 chunks 0 中抽離出了 index.bundle.css 文件。
參考:http://www.rzrgm.cn/skychx/p/webpack-module-chunk-bundle.html
2、manifest
在使用 webpack 構建的典型應用程序或站點中,有三種主要的代碼類型:
- 你或你的團隊編寫的源碼。
- 你的源碼會依賴的任何第三方的 library 或 "vendor" 代碼。
- webpack 的 runtime 和 manifest,管理所有模塊的交互。
2.1、runtime和manifest
runtime 以及伴隨的 manifest 數據,主要是指:在瀏覽器運行過程中,webpack 用來連接模塊化應用程序所需的所有代碼。它包含:在模塊交互時,連接模塊所需的加載和解析邏輯。包括:已經加載到瀏覽器中的連接模塊邏輯,以及尚未加載模塊的延遲加載邏輯。
webpack 和 webpack 插件是如何“知道”應該哪些文件生成的呢?答案是,webpack 通過 manifest,可以追蹤所有模塊到輸出 bundle 之間的映射。在你的應用程序中,形如 index.html 文件、一些 bundle 和各種資源,都必須以某種方式加載和鏈接到應用程序,一旦被加載到瀏覽器中。在經過打包、壓縮、為延遲加載而拆分為細小的 chunk 這些 webpack 優化 之后,你精心安排的 /src 目錄的文件結構都已經不再存在。所以 webpack 如何管理所有所需模塊之間的交互呢?這就是 manifest 數據用途的由來。
當 compiler 開始執行、解析和映射應用程序時,它會保留所有模塊的詳細要點。這個數據集合稱為 "manifest",當完成打包并發送到瀏覽器時,runtime 會通過 manifest 來解析和加載模塊。無論你選擇哪種 模塊語法,那些 import 或 require 語句現在都已經轉換為 __webpack_require__ 方法,此方法指向模塊標識符(module identifier)。通過使用 manifest 中的數據,runtime 將能夠檢索這些標識符,找出每個標識符背后對應的模塊。
通過使用內容散列(content hash)作為 bundle 文件的名稱,這樣在文件內容修改時,會計算出新的 hash,瀏覽器會使用新的名稱加載文件,從而使緩存無效。一旦你開始這樣做,你會立即注意到一些有趣的行為。即使某些內容明顯沒有修改,某些 hash 還是會改變。這是因為,注入的 runtime 和 manifest 在每次構建后都會發生變化。
3、Babel
Babel其實是一個編譯JavaScript的平臺,它可以編譯代碼幫你達到以下目的:
- 讓你能使用最新的JavaScript代碼(ES6,ES7...),而不用管新標準是否被當前使用的瀏覽器完全支持;
- 讓你能使用基于JavaScript進行了拓展的語言,比如React的JSX;
3.1、Babel的安裝與配置
Babel其實是幾個模塊化的包,其核心功能位于稱為babel-core的npm包中,webpack可以把其不同的包整合在一起使用,對于每一個你需要的功能或拓展,你都需要安裝單獨的包.
用得最多的是解析Es6的babel-preset-包和解析JSX的babel-preset-react包。
下面測試使用 babel 來解析支持 es6 和 react 語法。我們先來一次性安裝這些依賴包:
// npm一次性安裝多個依賴模塊,模塊之間用空格隔開 npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react
在webpack中配置Babel的方法如下:
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"http://打包后輸出文件的文件名
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉
inline: true//實時刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
options: {
presets: [
"env", "react"
]
}
},
exclude: /node_modules/
}
]
}
};
配置完以上選項后就能支持ES6以及JSX的語法了。測試:
npm install --save react react-dom
//Greeter,js
import React, {Component} from 'react'
import config from './config.json';
class Greeter extends Component{
render() {
return (
<div>
{config.greetText}
</div>
);
}
}
export default Greeter
// main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
render(<Greeter />, document.getElementById('root'));
直接編譯或者在本地服務器上就能看到運行結果
3.2、.babelrc 文件
Babel其實可以完全在 webpack.config.js 中進行配置,但是考慮到babel具有非常多的配置選項,在單一的webpack.config.js文件中進行配置往往使得這個文件顯得太復雜,因此一些開發者支持把babel的配置選項放在一個單獨的名為 ".babelrc" 的配置文件中。webpack會自動調用.babelrc里的babel配置選項
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"http://打包后輸出文件的文件名
},
devtool: 'eval-source-map',
devServer: {
contentBase: "./public",//本地服務器所加載的頁面所在的目錄
historyApiFallback: true,//不跳轉
inline: true//實時刷新
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
}
]
}
};
//.babelrc
{
"presets": ["react", "env"]
}
4、清理輸出文件夾的文件(CleanWebpackPlugin)
由于之前的一些編譯可能會導致用于輸出的文件夾中包含了一些沒必要的文件,顯得比較雜亂,我們可以用clean-webpack-plugin 插件清理輸出的文件夾中的文件。
該插件會在每次構建前清理輸出文件夾,只會生成用到的文件。
npm install clean-webpack-plugin --save-dev
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
print: './src/print.js'
},
plugins: [
new CleanWebpackPlugin(['public/*.*'], {
root: __dirname,
verbose: true, //開啟在控制臺輸出信息
dry: false
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
4.1、webpack-dev-server和CleanWebpackPlugin同時使用的問題
有一個問題是當熱加載和CleanWebpackPlugin插件同時使用時,運行dev-sever會重新編譯,由此也重新使用清理插件,目標文件夾中的文件將被全部刪除,但是此時并不生成新的文件。
雖然沒報什么問題,代碼也可以跑,但是還是困惑了我很久。這個問題應該是運行webpack-dev-server時webpack會重新編譯,由此使用了清理插件,但是dev-server并不會在工作目錄中生成編譯的文件,而是在內存中生成,所以看不到,但是代碼跑的沒問題。
要想解決這個問題,我在GitHub上看到了一個issue,但是嘗試了好像會報錯:https://github.com/johnagan/clean-webpack-plugin/issues/96。過后會繼續關注該問題。
5、resolve(解析)
這些選項能設置模塊如何被解析。
5.1、resolve.alias(配置模塊路徑別名)
在我們引入一些模塊文件時,可能文件路徑層次比較深,這樣的話就會導致相對路徑的寫法比較長。通過 resolve.alias 能夠將一些路徑配置成指定的別名,能夠讓我們簡寫路徑。
module.exports = { ... resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js', '@': resolve('src'), //由此引入src文件夾下的文件就可以寫成:import xxx from @/xxx.js } }, }
5.2、resolve.extensions(指定文件擴展名)
通過指定 resolve 字段的extensions值可以指定自動解析的文件擴展名。
webpack的默認配置為:
module.exports = { //... resolve: { extensions: ['.wasm', '.mjs', '.js', '.json'] } };
由此用戶在引入模塊一些文件時就可以不帶擴展,比如:
//引入 mian.js import aaa from '../path/to/main';
如果我們自定義該選項,就會覆蓋默認數組即webpack的默認配置,這就意味著 webpack 將不再嘗試使用默認擴展來解析模塊,對于需要指定自動解析的文件擴展名必須得寫入該數組中。
5.3、resolve.modules
告訴 webpack 解析模塊時應該去哪個目錄搜索該模塊。
webpack的默認配置為:
module.exports = { //... resolve: { modules: ['node_modules'] } };
6、devServer配置
webpack-dev-server 能夠用來啟動一個本地服務器,接受HTTP請求,通過這個服務器,可以使用HTTP協議進行頁面的效果測試(不啟動服務器就只能通過file://xxx的方式來看頁面)。除此之外,還能夠實現許多額外的功能,比如熱加載(一旦修改項目文件,就自動重新加載服務器)
6.1、devServer.proxy(服務器代理)
服務器代理功能一般用來解決開發環境跨域問題。
(跨域問題場景:由于 webpack-dev-server 是一個本地開發服務器,所以我們的應用在開發階段是獨立運行在 localhost 的一個端口上,而后端服務又是運行在另外一個地址上。但是最終上線過后,我們的應用一般又會和后端服務部署到同源地址下。那這樣就會出現一個非常常見的問題:在實際生產環境中能夠直接訪問的 API,回到我們的開發環境后,再次訪問這些 API 就會產生跨域請求問題。解決這種開發階段跨域請求問題最好的辦法,就是在開發服務器中配置一個后端 API 的代理服務,也就是把后端接口服務代理到本地的開發服務地址。webpack-dev-server 就支持直接通過配置的方式,添加代理服務。例子可參考:https://blog.csdn.net/zwkkkk1/article/details/81541057)
webpack proxy只能用作于開發階段,臨時解決本地請求服務器(通常是測試服)產生的跨域問題,并不適用線上環境,因為線上環境時根本就不會啟動 devserver 這個東西。
配置:
mmodule.exports = { //... devServer: { proxy: { '/api': 'http://localhost:3000' } } };
此時請求到 /api/xxx 現在會被代理到請求 http://localhost:3000/api/xxx, 例如 http://localhost:8080/api/user 現在會被代理到請求 http://localhost:3000/api/user
如果你不想始終傳遞 /api ,則需要重寫路徑。如下,此時請求到 /api/xxx 現在會被代理到請求 http://localhost:3000/xxx, 例如 http://localhost:8080/api/user 現在會被代理到請求 http://localhost:3000/user
module.exports = { //... devServer: { proxy: { '/api': { target: 'http://localhost:3000', pathRewrite: {'^/api' : ''} } } } };
要想解決跨域問題將 changeOrigin 參數設置為 true, 本地就會虛擬一個服務器接收你的請求并代你發送該請求:
module.exports = { //... devServer: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, } } } };
可參考:https://juejin.cn/post/6844903909002051592
6.1.1、devServer.proxy的原理
瀏覽器和服務器之間有跨域問題,而服務器和服務器之間就不會有跨域問題。前端代理解決跨域問題實際上就是通過啟動一個服務器,通過該服務器來代理前端請求,然后將請求發出,由此跟后端服務器就不會有跨域問題。
webpack中的proxy只是一層代理,用于把指定的路徑path,代理去后端服務的地址,背后使用node來做server。該技術只是webpack在本地開發階段臨時生成了node server,來實現類似nginx 的proxy_pass的反向代理效果。同源策略是瀏覽器的安全策略,不是HTTP協議的一部分。服務器端調用HTTP接口只是使用HTTP協議,不會執行JS腳本,不需要同源策略,所以也就不存在跨域問題。
proxy工作原理實質上是利用http-proxy-middleware 這個http代理中間件,實現請求轉發給其他服務器。
proxy 只是在開發環境適用,到生產環境還是需要服務端進行配置,基本就是將限制的請求頭進行修改。
6.1.2、http-proxy-middleware的基本介紹
http-proxy-middleware用于將請求轉發給其它服務器。
例如:我們當前主機 A 為http://localhost:3000/,現在瀏覽器發送一個請求,請求接口/api,這個請求的數據在另外一臺服務器B上(http://10.119.168.87:4000),這時,就可通過在 A 主機設置代理,直接將請求發送給B主機。
代碼示例:
var express = require('express'); var proxy = require('http-proxy-middleware'); var app = express(); app.use('/api', proxy({target: 'http://10.119.168.87:4000', changeOrigin: true})); app.listen(3000);
說明:我們利用 express 在 3000 端口啟動了一個小型的服務器,利用了app.use('/api', proxy({target: 'http://10.119.168.87:4000/', changeOrigin: true}))使發到 3000 端口的 /api 請求轉發到了4000端口。即請求http://localhost:3000/api相當于請求http://10.119.168.87:4000/api。
7、optimization(優化)
7.1、optimization.splitChunks
optimization.splitChunks 的默認配置如下:
module.exports = { //... optimization: { splitChunks: { chunks: 'async', minSize: 30000, //分割的chunk最小為30kb maxSize: 0, //最大沒有限制 minChunks: 1, //要提取的chunk最少應該被引用過一次 maxAsyncRequests: 5, //按需加載并行加載的文件的最大數量 maxInitialRequests: 3, //入口js文件最大并行請求數量 automaticNameDelimiter: '~', //名稱連接符 name: true, //可以使用命名規則 cacheGroups: { //分割chunk的組 vendors: { test: /[\\/]node_modules[\\/]/, //node_modules文件會被打包到 vendors 組的chunk中 --> vendors~xxx.js priority: -10 //優先級 }, default: { minChunks: 2, //要提取的chunk最少被引用2次 priority: -20, //優先級 reuseExistingChunk: true //如果當前要打包的模塊,和之前已經被提取的模塊是同一個,就會復用,而不是重新打包 } } } } };

浙公網安備 33010602011771號