前面已經實現了一個簡單webpack配置,接下來須要在前面的基礎上對webpack.config.js
進行拆分。javascript
項目開發時,咱們須要用webpack-dev-server
啓動開發服務器,當咱們修改文件時,它能自動從新打包項目並刷新頁面。css
項目打包上線時,咱們但願webpack能進行更多的處理來優化打包後的代碼。html
針對不一樣的需求,咱們須要將配置文件拆分爲webpack.common.js
webpack.dev.js
webpack.prod.js
。前端
項目代碼 Github 倉庫java
webpack.common.js
這個文件是共用的配置。node
const path = require("path");
module.exports = {
// 入口文件改成 .jsx文件
entry: "./src/index.jsx",
resolve: {
extensions: [".js", ".json", ".jsx"]
},
module: {
rules: [
{
test: /\.jsx?$/,
// include告訴webpack只對src下的
// js、jsx文件進行babel轉譯
// 加快webpack的打包速度
include: path.resolve(__dirname, "src"),
use: "babel-loader"
}
]
}
};
複製代碼
webpack.dev.js
咱們須要使用webpack-merge
將前面配好的webpack.comm.js
合併進來。並且須要webpack-dev-server
來啓動開發服務器。react
安裝:webpack
webpack-merge
webpack-dev-server
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const merge = require("webpack-merge");
const common = require("./webpack.common");
// 使用webpack-merge將webpack.common.js合併進來
module.exports = merge(common, {
// 設置爲開發(development)模式
mode: "development",
// 設置source map,方便debugger
devtool: "inline-source-map",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
publicPath: "/"
},
devServer: {
// 單頁應用的前端路由使用history模式時,這個配置很重要
// webpack-dev-server服務器接受的請求路徑沒有匹配的資源時
// 他會返回index.html而不是404頁面
historyApiFallback: true
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.(png|jpg|jpeg|svg|gif)$/,
use: "file-loader"
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/template.html",
favicon: "./src/assets/favicon-32x32-next.png"
})
]
});
複製代碼
webpack.prod.js
打包生產環境使用的代碼須要很是多的優化和處理,因此這個文件的配置會很是複雜。git
main.js
內抽離到單獨的.css
文件main.js
內抽離出來source-map
替代inline-source-map
dist
目錄前面的webpack.dev.js
只是簡單的使用了css-loader
和style-loader
。github
css-loader
將項目導入的css樣式轉爲js模塊,打包到main.js
內。
style-loader
在main.js
內提供了一個能將css動態插入到html內的方法。
當用戶打開頁面時,會先加載html,而後加載main.js
,最後運行js腳本將樣式插入到style
標籤內。
這樣有多個缺點:
main.js
內,增長了它的文件大小,並且也不方便對css作緩存main.js
腳本運行,並插入到html時纔會有效果。這個空檔期雖然很短,但它會形成界面閃爍。優勢:
因此style-loader
與css-loader
的組合僅適用於開發。咱們須要使用mini-css-extract
插件,並用該插件提供的loader來替代style-loader
。
// ...
plugins:[
// 配置mini-css-extract插件
new MiniCssExtractPlugin({
// 設置抽取出來的css名字
filename: "[name].[contentHash].css",
chunkFilename: "[id].[contentHash].css"
}),
// ...
],
// ...
複製代碼
// ...
module:{
rules:[
{
test: /\.css$/,
// 使用MiniCssExtractPlugin.loader替代style-loader
use: [MiniCssExtractPlugin.loader, "css-loader"]
},
// ...
]
},
// ...
複製代碼
緩存是前端頁面性能優化的重點。咱們但願瀏覽器能長久緩存資源,同時又能在第一時間獲取更新後的資源。
具體思路是:後端不對index.html
作任何緩存處理,對css、js、圖片等資源作持久緩存。將output.filename
配置爲"main.[contentHash].js"
,這樣打包後的main.js
中間會加上一段contentHash
。contentHash
是根據打包文件內容產生的,內容改變它纔會發生改變。發佈時,因爲哈希值不一樣,服務器能同時保存着不一樣哈希版本的資源。這樣保證了發佈過程當中,用戶仍然可以訪問到舊資源,而且新用戶會訪問到新資源。
加上contentHash
後打包文件名變成main.xxxxxx.js
,其中xxxxxx
表明一串很長的哈希值。
可是,如今咱們的業務代碼、引用的第三方庫,還有webpack生成的runtime都被捆綁打包到了main.xxxxxx.js
內。第三方庫和webpack的runtime變更的頻率很是低,因此咱們不但願每次業務代碼的改動致使用戶得連同它們一塊兒從新下載一遍。所以咱們須要將它們從main.xxxxxx.js
內抽離出來。
還有一點須要注意,webpack之前的版本有個小小的問題。在打包文件內容沒發生變化的狀況下contentHash
任然會發生改變。此時須要使用webpack.HashedModuleIdsPlugin
插件來替代默認的哈希生成。雖然webpack4修復了這個問題,可是官方文檔仍是推薦咱們使用webpack.HashedModuleIdsPlugin
插件。
前面已經說明了爲何要抽離他們。
之前的版本使用commons-chunk-plugin
插件來抽離第三方庫,webpack 4經過配置optimization.splitChunks
來抽取。內部其實使用了split-chunks-plugin
插件。
將optimization.runtimeChunk
選項配置爲single
,能夠將webpack runtime抽離到單文件中。
// ...
optimization: {
// 抽離webpack runtime到單文件
runtimeChunk: "single",
splitChunks: {
chunks: "all",
// 最大初始請求數量
maxInitialRequests: Infinity,
// 抽離體積大於80kb的chunk
minSize: 80 * 1024,
// 抽離被多個入口引用次數大於等於1的chunk
minChunks: 1,
cacheGroups: {
// 抽離node_modules下面的第三方庫
vendor: {
test: /[\\/]node_modules[\\/]/,
// 從模塊的路徑地址中得到庫的名稱
name: function(module, chunks, chacheGroupKey) {
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
return `vendor_${packageName.replace("@", "")}`;
}
}
}
},
// ...
},
// ...
複製代碼
打包後的js、css、html會有註釋、空格、換行,開啓代碼壓縮能夠大幅度減小資源的體積。
在mode
設爲"production"
時,會默認使用terser-webpack-plugin
插件對js進行壓縮。咱們還要開啓html與css的壓縮,因此要重寫optimization.minimizer
選項。
// ...
optimization: {
minimizer: [
// 壓縮css
new OptimizeCssAssetsWebpackPlugin(),
// 壓縮js,記得sourceMap設爲true
new TerserWebpackPlugin({ sourceMap: true }),
// 該插件還能對html進行壓縮
new HtmlWebpackPlugin({
template: "./src/template.html",
favicon: "./src/assets/favicon-32x32-next.png",
minify: {
// 摺疊空白符(去除換行符和空格)
collapseWhitespace: true,
// 移除註釋
removeComments: true,
// 移除屬性上沒必要要的引號
removeAttributeQuotes: true
}
})
],
// ...
}
// ...
複製代碼
source-map
替代inline-source-map
發生bug時,咱們很難經過打包後的代碼找出錯誤的源頭。因此咱們須要source map將代碼映射爲原來咱們手寫時候的樣子。
前面的webpack.dev.js
內使用的是inline-source-map
。它的缺點是將map內斂到了代碼內,這樣用戶會連同資源將map一塊兒下載。
因此咱們使用source-map
,它會給打包後的每一個js單獨生成.map
文件。
懶加載也叫按需加載。咱們當前打包的全部js會在頁面加載過程當中被加載運行。可是大多數狀況下,用戶並不會訪問應用的全部頁面與功能。咱們能夠將每一個頁面的代碼或一些不常使用的功能模塊作成按需加載,這樣能夠大大減少用戶初次訪問時所要加載的資源大小。
懶加載是webpack4默認支持的,不須要任何配置。前端人員須要在開發時使用dynamic import按需引入模塊。webpack會自動將dynamic import引入的模塊單獨打包爲一個chunk(注意:dynamic import語法上須要babel插件的支持,會在下一章節提到該插件)。
webpack官網提供的例子:
// print.js
console.log('The print.js module has loaded! See the network tab in dev tools...');
export default () => {
console.log('Button Clicked: Here\'s "some text"!');
};
複製代碼
// src/index.js
// ...
button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
var print = module.default;
print();
});
// ...
複製代碼
經過點擊按鈕,觸發加載print
模塊。/* webpackChunkName: "print" */
這個註釋告訴webpack該模塊打包成的chunk名字叫print
。
dist
目錄使用clean-webpack-plugin
在打包前清空dist
目錄。
webpack.prod.js
的完整配置安裝:
mini-css-extract-plugin
clean-webpack-plugin
terser-webpack-plugin
optimize-css-assets-webpack-plugin
url-loader
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const merge = require("webpack-merge");
const common = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const TerserWebpackPlugin = require("terser-webpack-plugin");
const OptimizeCssAssetsWebpackPlugin = require("optimize-css-assets-webpack-plugin");
const webpack = require("webpack");
module.exports = merge(common, {
// 設置爲生產(production)模式
mode: "production",
// 在生產環境中使用"source-map"而不是"inline-source-map"
devtool: "source-map",
output: {
// 這裏添加contentHash
// 因爲咱們的entry中沒有配置入口的名稱
// webpack會默認取名爲main
// 所以這裏的配置會生成"main.xxxxxx.js"
filename: "[name].[contentHash].js",
// 經過splitChunks抽離的js文件名格式
chunkFilename: "[name].[contentHash].chunk.js",
path: path.resolve(__dirname, "dist"),
publicPath: "/"
},
module: {
rules: [
{
test: /\.css$/,
// 這裏使用MiniCssExtractPlugin.loader替代style-loader
use: [MiniCssExtractPlugin.loader, "css-loader"]
},
{
test: /\.(png|jpg|jpeg|svg|gif)$/,
use: {
// 這裏使用url-loader替代file-loader
loader: "url-loader",
options: {
// 當圖片小於8kb時,url-loader會將圖片轉爲base64
// 這樣能夠減小http請求的數量
// 若是大於8kb的話,url-loader會將圖片交給file-loader處理
// 因此url-loader須要依賴file-loader
limit: 1024 * 8,
name: "img/[name].[hash:8].[ext]"
}
}
}
]
},
optimization: {
// 抽離webpack runtime到單文件
runtimeChunk: "single",
// 壓縮器
minimizer: [
// 壓縮css
new OptimizeCssAssetsWebpackPlugin(),
// 壓縮js,記得將sourceMap設爲true
// 不然會沒法生成source map
new TerserWebpackPlugin({ sourceMap: true }),
// 該插件還能壓縮html
new HtmlWebpackPlugin({
template: "./src/template.html",
favicon: "./src/assets/favicon-32x32-next.png",
minify: {
// 摺疊空白符
collapseWhitespace: true,
// 移除註釋
removeComments: true,
// 移除屬性多餘的引號
removeAttributeQuotes: true
}
})
],
splitChunks: {
chunks: "all",
// 最大初始請求數
maxInitialRequests: Infinity,
// 80kb以上的chunk抽離爲單獨的js文件
// 配合上面的 maxInitialRequests: Infinity
// 小於80kb的全部chunk會被打包一塊兒
// 這樣能夠減小初始請求數
// 你們能夠根據本身的狀況設置
minSize: 80 * 1024,
// 抽離多入口引用次數1以上的chunk
minChunks: 1,
cacheGroups: {
// 抽離node_modules內的第三方庫
vendor: {
test: /[\\/]node_modules[\\/]/,
// 根據路徑得到第三方庫的名稱
// 並將抽離的chunk以"vendor_thirdPartyLibrary"格式命名
name: function(module, chunks, chacheGroupKey) {
const packageName = module.context.match(
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
)[1];
return `vendor_${packageName.replace("@", "")}`;
}
}
}
}
},
plugins: [
// 每次打包前,先清除輸出目錄
new CleanWebpackPlugin(),
// 抽離css
new MiniCssExtractPlugin({
filename: "[name].[contentHash].css",
chunkFilename: "[id].[contentHash].css"
}),
// 確保在文件沒發生改變時,contentHash也不會變化
new webpack.HashedModuleIdsPlugin()
]
});
複製代碼
// ...
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
// ...
複製代碼
基本配置完成,能夠安裝react-router redux進行單頁應用開發了
npm i react-router-dom redux react-redux redux-thunk
複製代碼
下章節內容:添加babel插件支持decorator、類屬性與dynamic import;添加sass預處理;添加postcss Autoprefixer自動補充瀏覽器廠商前綴;使用.browserslistrc
配置須要兼容的瀏覽器範圍;添加Prettier ESLint來規範與格式化代碼;