超碰人人人人人,亚洲AV午夜福利精品一区二区,亚洲欧美综合区丁香五月1区,日韩欧美亚洲系列

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

JAVASCRIPT前端框架WEBPACK5從入門到精通

admin
2024年4月3日 11:2 本文熱度 1322

前言

webpack是什么?

摘自官網(wǎng)的一段話:webpack 是一個(gè)用于現(xiàn)代 JavaScript 應(yīng)用程序的 靜態(tài)模塊打包工具。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)在內(nèi)部從一個(gè)或多個(gè)入口點(diǎn)構(gòu)建一個(gè) 依賴圖(dependency graph),然后將你項(xiàng)目中所需的每一個(gè)模塊組合成一個(gè)或多個(gè) bundles,它們均為靜態(tài)資源,用于展示你的內(nèi)容。 官網(wǎng)鏈接:https://webpack.docschina.org/concepts/?

為什么需要打包程序

在日常的開(kāi)發(fā)中,我們會(huì)使用框架(React、Vue),ES6 模塊化語(yǔ)法,Less/Sass 等 css 預(yù)處理器等語(yǔ)法進(jìn)行開(kāi)發(fā)。這樣的代碼要想在瀏覽器運(yùn)行必須經(jīng)過(guò)編譯成瀏覽器能識(shí)別的 JS、Css 等語(yǔ)法,才能運(yùn)行,所以我們需要打包工具幫我們做完這些事。除此之外,打包工具還能壓縮代碼、做兼容性處理、提升代碼性能等。

正文

概念

在這里我將webpack為了幾個(gè)要點(diǎn),也是webpack核心的幾個(gè)點(diǎn)

entry(入口) 需要打包的入口文件,可以為一個(gè)入口,也可以為多個(gè)入口(有幾個(gè)入口就有幾個(gè)輸出),Webpack 本身功能比較少,只能處理 js 資源,一旦遇到 css 等其他資源就會(huì)報(bào)錯(cuò)output(輸出) 可以通過(guò)配置 output 選項(xiàng),告知 webpack 如何向硬盤寫入編譯文件。注意,即使可以存在多個(gè) entry 起點(diǎn),但只能指定一個(gè) output 配置。loaders(模塊解析器) loader 用于對(duì)模塊的源代碼進(jìn)行轉(zhuǎn)換。loader 可以使你在 import 或 “l(fā)oad(加載)” 模塊時(shí)預(yù)處理文件。因此,loader 類似于其他構(gòu)建工具中“任務(wù)(task)”,并提供了處理前端構(gòu)建步驟的得力方式。loader 可以將文件從不同的語(yǔ)言(如 TypeScript)轉(zhuǎn)換為 JavaScript 或?qū)?nèi)聯(lián)圖像轉(zhuǎn)換為 data URL。loader 甚至允許你直接在 JavaScript 模塊中 import CSS 文件!Plugins(插件) 插件 是 webpack 的 支柱 功能。Webpack 自身也是構(gòu)建于你在 webpack 配置中用到的 相同的插件系統(tǒng) 之上!插件目的在于解決 loader 無(wú)法實(shí)現(xiàn)的其他事mode(模式) 指示 Webpack 使用相應(yīng)模式的配置。 development 開(kāi)發(fā)模式:會(huì)將 process.env.NODE_ENV 的值設(shè)為 development。啟用 NameChunksPlugin 和 NameModulesPlugin。特點(diǎn)是能讓代碼本地調(diào)試運(yùn)行的環(huán)境。 production 生產(chǎn)模式:會(huì)將 process.env.NODE_ENV 的值設(shè)為 production。啟用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。特點(diǎn)是能讓代碼優(yōu)化上線運(yùn)行的環(huán)境。

起步

新建文件,創(chuàng)建一個(gè)新的項(xiàng)目

下載我們的依賴

構(gòu)建項(xiàng)目目錄文件

webpack_document # 項(xiàng)目根目錄(所有指令必須在這個(gè)目錄運(yùn)行)

└── src # 項(xiàng)目源碼目錄

├── js # js文件目錄

│ ├── count.js

│ └── sum.js

├── css # css文件目錄

└── index.js # 項(xiàng)目主文件

創(chuàng)建我們的配置文件webpack.config.js

運(yùn)行指令

觀察生成的dist的文件,就是我們打包后的文件了 Webpack 將來(lái)都通過(guò) webpack.config.js 文件進(jìn)行配置,來(lái)增強(qiáng) Webpack 的功能

開(kāi)發(fā)模式介紹

開(kāi)發(fā)模式顧名思義就是我們開(kāi)發(fā)代碼時(shí)使用的模式。 這個(gè)模式下我們主要做兩件事:

編譯代碼,使瀏覽器能識(shí)別運(yùn)行開(kāi)發(fā)時(shí)我們有樣式資源、字體圖標(biāo)、圖片資源、html 資源等,webpack 默認(rèn)都不能處理這些資源,所以我們要加載配置來(lái)編譯這些資源代碼質(zhì)量檢查,樹(shù)立代碼規(guī)范提前檢查代碼的一些隱患,讓代碼運(yùn)行時(shí)能更加健壯。提前檢查代碼規(guī)范和格式,統(tǒng)一團(tuán)隊(duì)編碼風(fēng)格,讓代碼更優(yōu)雅美觀。

處理樣式資源

由于webpack只能處理js、json文件,并不能處理css文件,所以我們需要借助相應(yīng)loader解析器來(lái)增強(qiáng)我們的功能,在webpack的官網(wǎng)中,為我們提供了常用的loader,如果不能滿足我們的日常需要,也可以到社區(qū)中去尋找想要的loader [webpack官網(wǎng)loader]

處理css資源

下載

說(shuō)明

css-loader:負(fù)責(zé)將 Css 文件編譯成 Webpack 能識(shí)別的模塊style-loader:會(huì)動(dòng)態(tài)創(chuàng)建一個(gè) Style 標(biāo)簽,里面放置 Webpack 中 Css 模塊內(nèi)容 此時(shí)樣式就會(huì)以 Style 標(biāo)簽的形式在頁(yè)面上生效

配置

使用 在src目錄下創(chuàng)建我們的css文件,并在index.js中進(jìn)行引入,然后進(jìn)行打包,觀察我們的dist文件里面的輸出結(jié)果 為了方便我們觀察效果,我們創(chuàng)建我們靜態(tài)頁(yè)面,并引入我們打包后dist文件下面的js文件,文件目錄結(jié)構(gòu)如下 頁(yè)面效果如下:

處理less資源

下載

說(shuō)明

less-loader:負(fù)責(zé)將 Less 文件編譯成 Css 文件

配置

使用 在src目錄下,創(chuàng)建我們的less文件夾,并生成index.less文件,寫入樣式,在index.js,引入我們的less文件,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的box文件,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果如下:

處理sass/scss資源

下載

說(shuō)明

sass-loader:負(fù)責(zé)將 Sass 文件編譯成 css 文件sass:sass-loader 依賴 sass 進(jìn)行編譯

配置

使用 在src目錄下,創(chuàng)建我們的sass文件夾,并生成index.sass 和 index.scss 文件,寫入樣式,在index.js,引入我們的sass、scss文件,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的box文件,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果如下:

處理 Styl 資源

說(shuō)明

stylus-loader:負(fù)責(zé)將 Styl 文件編譯成 Css 文件

配置

使用

在src目錄下,創(chuàng)建我們的styl文件夾,并生成index.styl 和 index.styl 文件,寫入樣式,在index.js,引入我們的styl文件,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的box文件,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果如下:

資源模塊

以下摘自官網(wǎng)的一段話 資源模塊(asset module)是一種模塊類型,它允許使用資源文件(字體,圖標(biāo)等)而無(wú)需配置額外 loader。 在 webpack 5 之前,通常使用:

raw-loader 將文件導(dǎo)入為字符串

url-loader 將文件作為 data URI 內(nèi)聯(lián)到 bundle 中

file-loader 將文件發(fā)送到輸出目錄

資源模塊類型(asset module type),通過(guò)添加 4 種新的模塊類型,來(lái)替換所有這些 loader:

asset/resource 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL。之前通過(guò)使用 file-loader 實(shí)現(xiàn)。

asset/inline 導(dǎo)出一個(gè)資源的 data URI。之前通過(guò)使用 url-loader 實(shí)現(xiàn)。

asset/source 導(dǎo)出資源的源代碼。之前通過(guò)使用 raw-loader 實(shí)現(xiàn)。

asset 在導(dǎo)出一個(gè) data URI 和發(fā)送一個(gè)單獨(dú)的文件之間自動(dòng)選擇。之前通過(guò)使用 url-loader,并且配置資源體積限制實(shí)現(xiàn)。

當(dāng)在 webpack 5 中使用舊的 assets loader(如 file-loader/url-loader/raw-loader 等)和 asset 模塊時(shí),你可能想停止當(dāng)前 asset 模塊的處理,并再次啟動(dòng)處理,這可能會(huì)導(dǎo)致 asset 重復(fù),你可以通過(guò)將 asset 模塊的類型設(shè)置為 ‘javascript/auto’ 來(lái)解決。

處理圖片資源

配置

使用

在src目錄下,創(chuàng)建我們的images文件夾,添加圖片文件,筆者這里添加了jpe,png,gif三種格式的圖片,分別在不同的樣式中進(jìn)行引入,執(zhí)行npx webpack,觀察打包后的結(jié)果

此時(shí)如果查看 dist 目錄的話,會(huì)發(fā)現(xiàn)多了三張圖片資源

因?yàn)?Webpack 會(huì)將所有打包好的資源輸出到 dist 目錄下

為什么樣式資源沒(méi)有呢?

因?yàn)榻?jīng)過(guò) style-loader 的處理,樣式資源打包到 main.js 里面去了,所以沒(méi)有額外輸出出來(lái)

頁(yè)面效果

圖片資源處理優(yōu)化

將小于某個(gè)大小的圖片轉(zhuǎn)化成 data URI 形式(Base64 格式)

優(yōu)點(diǎn):減少請(qǐng)求數(shù)量

缺點(diǎn):體積變得更大,但是10kb以下的圖片,轉(zhuǎn)換為base64格式的情形下,只有增加1-2kb(所以我們出來(lái)10kb以下的)

配置

修改輸出資源的名稱和路徑

我們發(fā)現(xiàn)打包完成后,圖片直接輸出到dist的根目錄下了,沒(méi)有規(guī)范起來(lái),我們希望輸出到dist的static/images目錄下,同時(shí)js文件輸出到dist的static/js目錄下,這樣就比較規(guī)范了 

配置

使用 通過(guò)以上配置后,dist文件輸出的文件被規(guī)范起來(lái)了,這個(gè)時(shí)候需要修改一下我們index.html的引入路徑,打開(kāi)頁(yè)面也是同樣的效果

處理字體圖標(biāo)資源

當(dāng)我們?cè)陧?xiàng)目中使用字體圖標(biāo)的時(shí)候,我們也希望打包的時(shí)候,將這一部分內(nèi)容進(jìn)行打包輸出 

配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require("path");

module.exports = {

    // 入口

    entry: {

        // 需要一個(gè)相對(duì)路徑

        index: "./src/index.js",

    },

    // 輸出

    output: {

        // 需要一個(gè)絕對(duì)路徑

        path: path.resolve(__dirname, "dist"),

        clean: true,

        filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中

    },

    // 解析器

    module: {

        rules: [

            {

                test: /\.css$/,

                // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

                // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

                // loader:"style-loader"

                use: ["style-loader", "css-loader"],

            },

            {

                // 正則匹配所有已.less文件結(jié)尾的文件

                test: /\.less$/,

                // loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

                // 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

                // loader:"style-loader"

                use: ["style-loader", "css-loader", "less-loader"],

            },

            {

                test: /\.s[ac]ss$/,

                use: ["style-loader", "css-loader", "sass-loader"],

            },

            {

                test: /\.styl$/,

                use: ["style-loader", "css-loader", "stylus-loader"],

            },

            {

                test: /\.(png|jpe?g|gif|webp)$/,

                type: "asset",

                // 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

                parser: {

                    // 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

                    dataUrlCondition: {

                        maxSize: 10 * 1024, // 小于10kb的圖片會(huì)被base64處理

                    },

                },

                // 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

                generator: {

                    // 將圖片文件輸出到 static/imgs 目錄中

                    // 將圖片文件命名 [hash:8][ext][query]

                    // [hash:8]: hash值取8位

                    // [ext]: 使用之前的文件擴(kuò)展名

                    // [query]: 添加之前的query參數(shù)

                    filename: "static/imgs/[hash:8][ext][query]",

                },

            },

            {

                test: /\.(ttf|woff2?|svg)$/,

                // 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

                type: "asset/resource",

                generator: {

                    filename: "static/media/[hash:8][ext][query]",

                },

            },

        ],

    },

    plugins: [],

    mode: "production",

};

使用 在src目錄下,創(chuàng)建我們的fonts文件夾,在阿里巴巴適量庫(kù)中下載字體圖標(biāo)(筆者這里為了演示,使用的是font-class的形式),然后引入到fonts文件夾中,在index.js,然后在pubilc的index.html文件中,創(chuàng)建對(duì)應(yīng)的span標(biāo)簽,添加class類名,執(zhí)行npx webpack,觀察打包后的結(jié)果 頁(yè)面效果

處理其他資源

開(kāi)發(fā)中可能還存在一些其他資源,如音視頻等,我們也一起處理了

就是在處理字體圖標(biāo)資源基礎(chǔ)上增加其他文件類型,統(tǒng)一處理即可

配置

js資源處理

這里有同學(xué)可能會(huì)問(wèn),js 資源 Webpack 不能已經(jīng)處理了嗎,為什么我們還要處理呢? 原因是 Webpack 對(duì) js 處理是有限的,只能編譯 js 中 ES 模塊化語(yǔ)法,不能編譯其他語(yǔ)法,導(dǎo)致 js 不能在 IE 等瀏覽器運(yùn)行,所以我們希望做一些兼容性處理。 其次開(kāi)發(fā)中,團(tuán)隊(duì)對(duì)代碼格式是有嚴(yán)格要求的,我們不能由肉眼去檢測(cè)代碼格式,需要使用專業(yè)的工具來(lái)檢測(cè)。 針對(duì) js 兼容性處理,我們使用 Babel 來(lái)完成 針對(duì)代碼格式,我們使用 Eslint 來(lái)完成 我們先完成 Eslint,檢測(cè)代碼格式無(wú)誤后,在由 Babel 做代碼兼容性處理

ESLint

介紹 簡(jiǎn)介:可組裝的 JavaScript 和 JSX 檢查工具。 這句話意思就是:它是用來(lái)檢測(cè) js 和 jsx 語(yǔ)法的工具,可以配置各項(xiàng)功能 我們使用 Eslint,關(guān)鍵是寫 Eslint 配置文件,里面寫上各種 rules 規(guī)則,將來(lái)運(yùn)行 Eslint 時(shí)就會(huì)以寫的規(guī)則對(duì)代碼進(jìn)行檢查 配置文件 配置文件由很多種寫法:

.eslintrc.*:新建文件,位于項(xiàng)目根目錄

.eslintrc

.eslintrc.js

.eslintrc.json

區(qū)別在于配置格式不一樣

package.json 中 eslintConfig:不需要?jiǎng)?chuàng)建文件,在原有文件基礎(chǔ)上寫

ESLint 會(huì)查找和自動(dòng)讀取它們,所以以上配置文件只需要存在一個(gè)即可

具體配置 我們以.eslintrc.js為例子進(jìn)行配置 具體eslint的配置參考官網(wǎng)和規(guī)則文檔,這里給出相關(guān)鏈接 ESlint規(guī)則 ESlint官網(wǎng)

下載

npm i eslint-webpack-plugin eslint -D

在webpack中使用

vsocde安裝eslint插件 安裝eslint插件后,配置需要忽略檢查的文件

// .eslintignore

# 忽略dist目錄下所有文件

dist

Babel

介紹 JavaScript 編譯器。 主要用于將 ES6 語(yǔ)法編寫的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語(yǔ)法,以便能夠運(yùn)行在當(dāng)前和舊版本的瀏覽器或其他環(huán)境中

配置文件

配置文件由很多種寫法:

babel.config.*:新建文件,位于項(xiàng)目根目錄

babel.config.js

babel.config.json

.babelrc.*:新建文件,位于項(xiàng)目根目錄

.babelrc

.babelrc.js

.babelrc.json

package.json 中 babel:不需要?jiǎng)?chuàng)建文件,在原有文件基礎(chǔ)上寫

Babel 會(huì)查找和自動(dòng)讀取它們,所以以上配置文件只需要存在一個(gè)即可

具體配置 我們以babel.config.js為例子

// babel.config.js

module.exports = {

// 預(yù)設(shè)

presets: [],

};

presets 預(yù)設(shè)

簡(jiǎn)單理解:就是一組 Babel 插件, 擴(kuò)展 Babel 功能

@babel/preset-env: 一個(gè)智能預(yù)設(shè),允許您使用最新的 JavaScript。

@babel/preset-react:一個(gè)用來(lái)編譯 React jsx 語(yǔ)法的預(yù)設(shè)

@babel/preset-typescript:一個(gè)用來(lái)編譯 TypeScript 語(yǔ)法的預(yù)設(shè)

下載

npm i babel-loader @babel/core @babel/preset-env -D

配置

HTML資源處理

在實(shí)際的開(kāi)發(fā)工作中,我們希望使用一個(gè)html模塊,然后將我們打包后的js文件自動(dòng)引入到html模版中,這樣我們開(kāi)發(fā)的時(shí)候就不用手動(dòng)引入或者修改打包后的文件了

插件

下載

npm i html-webpack-plugin -D

配置

css資源處理

Css 文件目前被打包到 js 文件中,當(dāng) js 文件加載時(shí),會(huì)創(chuàng)建一個(gè) style 標(biāo)簽來(lái)生成樣式 這樣對(duì)于網(wǎng)站來(lái)說(shuō),會(huì)出現(xiàn)閃屏現(xiàn)象,用戶體驗(yàn)不好 我們應(yīng)該是單獨(dú)的 Css 文件,通過(guò) link 標(biāo)簽加載性能才好

提取css為單獨(dú)文件

下載插件

npm i mini-css-extract-plugin -D

配置

css兼容性處理

下載

npm i postcss-loader postcss postcss-preset-env -D

配置

配置需要兼容的瀏覽器列表

// package.json

"browserslist": [

"last 2 version",

"> 1%",

"not dead"

]

}

css壓縮

下載

npm i css-minimizer-webpack-plugin -D

配置

合并樣式處理配置

開(kāi)發(fā)服務(wù)器&自動(dòng)化

在我們的日常開(kāi)發(fā)中我們希望我改了代碼后,會(huì)自動(dòng)更新我們更改后的內(nèi)容,而不用每次打包后才能看到效果,所以webpack提供了一個(gè)devServer的配置 下載

npm i webpack-dev-server -D

配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

// 需要一個(gè)絕對(duì)路徑

path: path.resolve(__dirname, 'dist'),

clean: true,

filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中

},

// 解析器

module: {

rules: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: ["style-loader", "css-loader"],

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: ["style-loader", "css-loader", "less-loader"],

},

{

test: /\.s[ac]ss$/,

use: ["style-loader", "css-loader", "sass-loader"],

},

{

test: /\.styl$/,

use: ["style-loader", "css-loader", "stylus-loader"],

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代碼不編譯

loader: "babel-loader",

},

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "public/index.html"),

})

],

// 開(kāi)發(fā)服務(wù)器

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

},

mode: 'production'

}

運(yùn)行

npx webpack serve

并且當(dāng)你使用開(kāi)發(fā)服務(wù)器時(shí),所有代碼都會(huì)在內(nèi)存中編譯打包,并不會(huì)輸出到 dist 目錄下。 開(kāi)發(fā)時(shí)我們只關(guān)心代碼能運(yùn)行,有效果即可,至于代碼被編譯成什么樣子,我們并不需要知道。

開(kāi)發(fā)模式與生產(chǎn)模式

在我們?nèi)粘i_(kāi)發(fā)中,我們總是希望我們開(kāi)發(fā)完成后,今天一系列打包優(yōu)化,性能提升到最好,所以webpack這里也給我們提供了使用不同的配置文件進(jìn)行配置

開(kāi)發(fā)模式

適用于dev環(huán)境,配合devServe配置,進(jìn)行開(kāi)發(fā),并自動(dòng)實(shí)現(xiàn)代碼更新,所有代碼都會(huì)在內(nèi)存中編譯打包,并不會(huì)輸出到 dist 目錄下。

生產(chǎn)模式

生產(chǎn)模式是開(kāi)發(fā)完成代碼后,我們需要得到代碼將來(lái)部署上線。 這個(gè)模式下我們主要對(duì)代碼進(jìn)行優(yōu)化,讓其運(yùn)行性能更好。 優(yōu)化主要從兩個(gè)角度出發(fā):

優(yōu)化代碼運(yùn)行性能優(yōu)化代碼打包速度

使用

我們分別準(zhǔn)備兩個(gè)配置文件來(lái)放不同的配置,分別對(duì)應(yīng)開(kāi)發(fā)模式和生產(chǎn)模式

目錄配置

├── webpack-document (項(xiàng)目根目錄)

├── config (Webpack配置文件目錄)

│ ├── webpack.dev.js(開(kāi)發(fā)模式配置文件)

│ └── webpack.prod.js(生產(chǎn)模式配置文件)

├── node_modules (下載包存放目錄)

├── src (項(xiàng)目源碼目錄,除了html其他都在src里面)

│ └── 略

├── public (項(xiàng)目html文件)

│ └── index.html

├── .eslintrc.js(Eslint配置文件)

├── babel.config.js(Babel配置文件)

└── package.json (包的依賴管理配置文件)

生產(chǎn)不同的webpack配置,并做里面的絕對(duì)路徑進(jìn)行相應(yīng)的修改

webpack.dev.js配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄

filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中

// clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

// 解析器

module: {

rules: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代碼不編譯

loader: "babel-loader",

},

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 開(kāi)發(fā)服務(wù)器

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

},

mode: 'development'

}

webpack.prod.js配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

// 需要一個(gè)絕對(duì)路徑

path: path.resolve(__dirname, '../dist'),

clean: true,

filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

// 解析器

module: {

rules: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代碼不編譯

loader: "babel-loader",

},

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 開(kāi)發(fā)服務(wù)器

// devServer: {

// host: "localhost", // 啟動(dòng)服務(wù)器域名

// port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

// open: true, // 是否自動(dòng)打開(kāi)瀏覽器

// },

mode: 'production'

}

package.json配置

{

"name": "webpack_document",

"version": "1.0.0",

"description": "",

"main": "index.js",

// 為了方便運(yùn)行不同模式的指令,我們將指令定義在 package.json 中 scripts 里面

"scripts": {

"start": "npx webpack serve --config ./config/webpack.dev.js",

"dev": "npx webpack serve --config ./config/webpack.dev.js",

"build": "npx webpack --config ./config/webpack.prod.js",

"test": "echo \"Error: no test specified\" && exit 1"

},

"author": "",

"license": "ISC",

"devDependencies": {

"@babel/core": "^7.20.12",

"@babel/preset-env": "^7.20.2",

"babel-loader": "^9.1.2",

"css-loader": "^6.7.3",

"eslint": "^8.33.0",

"eslint-webpack-plugin": "^3.2.0",

"html-webpack-plugin": "^5.5.0",

"less-loader": "^11.1.0",

"sass": "^1.58.0",

"sass-loader": "^13.2.0",

"style-loader": "^3.3.1",

"stylus-loader": "^7.1.0",

"webpack": "^5.75.0",

"webpack-cli": "^5.0.1",

"webpack-dev-server": "^4.11.1"

}

}

開(kāi)發(fā)模式:npm start 或 npm run dev 生產(chǎn)模式:npm run build

基礎(chǔ)總結(jié)

如果你學(xué)習(xí)到這里,恭喜你現(xiàn)在已經(jīng)是一個(gè)webpack小能手了

兩種開(kāi)發(fā)模式

開(kāi)發(fā)模式:代碼能編譯自動(dòng)化運(yùn)行

生產(chǎn)模式:代碼編譯優(yōu)化輸出

Webpack 基本功能

開(kāi)發(fā)模式:可以編譯 ES Module 語(yǔ)法

生產(chǎn)模式:可以編譯 ES Module 語(yǔ)法,壓縮 js 代碼

Webpack 配置文件

5 個(gè)核心概念

entry

output

loader

plugins

mode

devServer 配置

Webpack 腳本指令用法

webpack 直接打包輸出

webpack serve 啟動(dòng)開(kāi)發(fā)服務(wù)器,內(nèi)存編譯打包沒(méi)有輸出

高級(jí)

提升開(kāi)發(fā)體驗(yàn)之sourceMap

在開(kāi)發(fā)過(guò)程中,我們運(yùn)行的代碼是經(jīng)過(guò)webpack編譯后的代碼,如果代碼出錯(cuò)了,我們想看到代碼的映射為我們自己的代碼,這個(gè)時(shí)候就需要sourceMap 了

介紹

SourceMap(源代碼映射)是一個(gè)用來(lái)生成源代碼與構(gòu)建后代碼一一映射的文件的方案。

它會(huì)生成一個(gè) xxx.map 文件,里面包含源代碼和構(gòu)建后代碼每一行、每一列的映射關(guān)系。當(dāng)構(gòu)建后代碼出錯(cuò)了,會(huì)通過(guò) xxx.map 文件,從構(gòu)建后代碼出錯(cuò)位置找到映射后源代碼出錯(cuò)位置,從而讓瀏覽器提示源代碼文件出錯(cuò)位置,幫助我們更快的找到錯(cuò)誤根源。

使用

通過(guò)查看Webpack DevTool 文檔文檔可知,SourceMap 的值有很多種情況. 但實(shí)際開(kāi)發(fā)時(shí)我們只需要關(guān)注兩種情況即可:

開(kāi)發(fā)模式:cheap-module-source-map

優(yōu)點(diǎn):打包編譯速度快,只包含行映射

缺點(diǎn):沒(méi)有列映射

module.exports = {

// 其他省略

mode: "development",

devtool: "cheap-module-source-map",

};

生產(chǎn)模式:source-map

優(yōu)點(diǎn):包含行/列映射

缺點(diǎn):打包編譯速度更慢

module.exports = {

// 其他省略

mode: "production",

devtool: "source-map",

};

提升打包構(gòu)建速度

介紹

開(kāi)發(fā)時(shí)我們修改了其中一個(gè)模塊代碼,Webpack 默認(rèn)會(huì)將所有模塊全部重新打包編譯,速度很慢。 所以我們需要做到修改某個(gè)模塊代碼,就只有這個(gè)模塊代碼需要重新打包編譯,其他模塊不變,這樣打包速度就能很快。

HotModuleReplacement(HMR/熱模塊替換):在程序運(yùn)行中,替換、添加或刪除模塊,而無(wú)需重新加載整個(gè)頁(yè)面。

使用

module.exports = {

// 其他省略

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了)

},

};

此時(shí) css 樣式經(jīng)過(guò) style-loader 處理,已經(jīng)具備 HMR 功能了。 但是 js 還不行。 JS 配置需要手動(dòng)進(jìn)行屬性需要進(jìn)行熱模塊使用的功能

// 判斷是否支持HMR功能

if (module.hot) {

module.hot.accept("./js/sum");

}

這樣如果我們自己搭建腳手架,每個(gè)引入的文件都需要進(jìn)行這樣更新一次,這里我們推薦使用社區(qū)的loader進(jìn)行解決,比如:vue-loader, react-hot-loader。

使用oneof對(duì)loader進(jìn)行處理

介紹

打包時(shí)每個(gè)文件都會(huì)經(jīng)過(guò)所有 loader 處理,雖然因?yàn)?test 正則原因?qū)嶋H沒(méi)有處理上,但是都要過(guò)一遍。比較慢。

規(guī)則數(shù)組,當(dāng)規(guī)則匹配時(shí),只使用第一個(gè)匹配規(guī)則。

使用

我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄

filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中

// clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每個(gè)文件只能被其中的一個(gè)loader處理

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

exclude: /node_modules/, // 排除node_modules代碼不編譯

loader: "babel-loader",

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 開(kāi)發(fā)服務(wù)器

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了)

},

mode: 'development'

}

Include/Exclude

介紹

開(kāi)發(fā)時(shí)我們需要使用第三方的庫(kù)或插件,所有文件都下載到 node_modules 中了。而這些文件是不需要編譯可以直接使用的。

所以我們?cè)趯?duì) js 文件處理時(shí),要排除 node_modules 下面的文件。(只針對(duì)js文件進(jìn)行處理,只有babel和eslint在進(jìn)行工作)

注意:以下兩個(gè)配置只能寫一種

include

包含,只處理 xxx 文件

exclude

排除,除了 xxx 文件以外其他文件都處理

配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

// 需要一個(gè)絕對(duì)路徑

path: path.resolve(__dirname, '../dist'),

clean: true,

filename: "static/js/index.js", // 將 js 文件輸出到 static/js 目錄中

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

loader: "babel-loader",

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 開(kāi)發(fā)服務(wù)器

// devServer: {

// host: "localhost", // 啟動(dòng)服務(wù)器域名

// port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

// open: true, // 是否自動(dòng)打開(kāi)瀏覽器

// },

mode: 'production'

}

cache緩存

介紹

每次打包時(shí) js 文件都要經(jīng)過(guò) Eslint 檢查 和 Babel 編譯,速度比較慢。

我們可以緩存之前的 Eslint 檢查 和 Babel 編譯結(jié)果,這樣第二次打包時(shí)速度就會(huì)更快了。

對(duì) Eslint 檢查 和 Babel 編譯結(jié)果進(jìn)行緩存。

配置

我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄

filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中

// clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每個(gè)文件只能被其中的一個(gè)loader處理

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

},

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

new CssMinimizerPlugin()

],

// 開(kāi)發(fā)服務(wù)器

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了)

},

mode: 'development'

}

Thead多進(jìn)程打包

介紹

當(dāng)項(xiàng)目越來(lái)越龐大時(shí),打包速度越來(lái)越慢,甚至于需要一個(gè)下午才能打包出來(lái)代碼。這個(gè)速度是比較慢的。

我們想要繼續(xù)提升打包速度,其實(shí)就是要提升 js 的打包速度,因?yàn)槠渌募急容^少。

而對(duì) js 文件處理主要就是 eslint 、babel、Terser 三個(gè)工具,所以我們要提升它們的運(yùn)行速度。

我們可以開(kāi)啟多進(jìn)程同時(shí)處理 js 文件,這樣速度就比之前的單進(jìn)程打包更快了。

多進(jìn)程打包:開(kāi)啟電腦的多個(gè)進(jìn)程同時(shí)干一件事,速度更快。

需要注意:請(qǐng)僅在特別耗時(shí)的操作中使用,因?yàn)槊總€(gè)進(jìn)程啟動(dòng)就有大約為 600ms 左右開(kāi)銷。

使用

下載

npm i thread-loader -D

獲取cpu核數(shù)

// nodejs核心模塊,直接使用

const os = require("os");

// cpu核數(shù)

const threads = os.cpus().length;

配置 我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

// cpu核數(shù)

const threads = os.cpus().length;

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄

filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中

// clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每個(gè)文件只能被其中的一個(gè)loader處理

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 開(kāi)啟多進(jìn)程

options: {

workers: threads, // 數(shù)量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

},

},

],

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

},

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 開(kāi)啟多進(jìn)程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

// new CssMinimizerPlugin()

],

// 推薦將壓縮操作放在這里

optimization: {

minimize: true,

minimizer: [

// css壓縮也可以寫到optimization.minimizer里面,效果一樣的

new CssMinimizerPlugin(),

// 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了

new TerserPlugin({

parallel: threads // 開(kāi)啟多進(jìn)程

})

],

},

// 開(kāi)發(fā)服務(wù)器

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了)

},

mode: 'development'

}

減少代碼體積 Tree Shaking

介紹

開(kāi)發(fā)時(shí)我們定義了一些工具函數(shù)庫(kù),或者引用第三方工具函數(shù)庫(kù)或組件庫(kù)。

如果沒(méi)有特殊處理的話我們打包時(shí)會(huì)引入整個(gè)庫(kù),但是實(shí)際上可能我們可能只用上極小部分的功能。

這樣將整個(gè)庫(kù)都打包進(jìn)來(lái),體積就太大了。

Tree Shaking 是一個(gè)術(shù)語(yǔ),通常用于描述移除 JavaScript 中的沒(méi)有使用上的代碼。

注意:它依賴 ES Module

使用

Webpack 已經(jīng)默認(rèn)開(kāi)啟了這個(gè)功能,無(wú)需其他配置。

減少babel文件生成的體積

介紹

Babel 為編譯的每個(gè)文件都插入了輔助代碼,使代碼體積過(guò)大!

Babel 對(duì)一些公共方法使用了非常小的輔助代碼,比如 _extend。默認(rèn)情況下會(huì)被添加到每一個(gè)需要它的文件中。

你可以將這些輔助代碼作為一個(gè)獨(dú)立模塊,來(lái)避免重復(fù)引入。

@babel/plugin-transform-runtime: 禁用了 Babel 自動(dòng)對(duì)每個(gè)文件的 runtime 注入,而是引入

@babel/plugin-transform-runtime 并且使所有輔助代碼從這里引用。

使用

下載

npm i @babel/plugin-transform-runtime -D

配置 我們的生產(chǎn)模式和開(kāi)發(fā)模式都需要配置,這里筆者展示開(kāi)發(fā)模式的配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

// cpu核數(shù)

const threads = os.cpus().length;

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄

filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中

// clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每個(gè)文件只能被其中的一個(gè)loader處理

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 開(kāi)啟多進(jìn)程

options: {

workers: threads, // 數(shù)量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 開(kāi)啟多進(jìn)程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

// new CssMinimizerPlugin()

],

// 推薦將壓縮操作放在這里

optimization: {

minimize: true,

minimizer: [

// css壓縮也可以寫到optimization.minimizer里面,效果一樣的

new CssMinimizerPlugin(),

// 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了

new TerserPlugin({

parallel: threads // 開(kāi)啟多進(jìn)程

})

],

},

// 開(kāi)發(fā)服務(wù)器

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了)

},

mode: 'development'

}

Image Minimizer 圖片壓縮

介紹

開(kāi)發(fā)如果項(xiàng)目中引用了較多圖片,那么圖片體積會(huì)比較大,將來(lái)請(qǐng)求速度比較慢。

我們可以對(duì)圖片進(jìn)行壓縮,減少圖片體積。

注意:如果項(xiàng)目中圖片都是在線鏈接,那么就不需要了。本地項(xiàng)目靜態(tài)圖片才需要進(jìn)行壓縮。

image-minimizer-webpack-plugin: 用來(lái)壓縮圖片的插件

使用

下載

npm i image-minimizer-webpack-plugin imagemin -D

還有剩下包需要下載,有兩種模式:(如果npm下載不下來(lái),使用cnpm下載)

無(wú)損壓縮

npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D

有損壓縮

npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D

配置 以無(wú)損壓縮為例子,(生產(chǎn)模式與開(kāi)發(fā)模式都需要配置)

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

// cpu核數(shù)

const threads = os.cpus().length;

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

path: undefined, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要指定輸出目錄

filename: "static/js/main.js", // 將 js 文件輸出到 static/js 目錄中

// clean: true, // 開(kāi)發(fā)模式?jīng)]有輸出,不需要清空輸出結(jié)果

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "cheap-module-source-map",

// 解析器

module: {

rules: [

{

// 每個(gè)文件只能被其中的一個(gè)loader處理

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

generator: {

// 將圖片文件輸出到 static/imgs 目錄中

// 將圖片文件命名 [hash:8][ext][query]

// [hash:8]: hash值取8位

// [ext]: 使用之前的文件擴(kuò)展名

// [query]: 添加之前的query參數(shù)

filename: "static/imgs/[hash:8][ext][query]",

},

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

generator: {

filename: "static/media/[hash:8][ext][query]",

},

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 開(kāi)啟多進(jìn)程

options: {

workers: threads, // 數(shù)量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 開(kāi)啟多進(jìn)程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/index.css",

}),

// new CssMinimizerPlugin()

],

// 推薦將壓縮操作放在這里

optimization: {

minimize: true,

minimizer: [

// css壓縮也可以寫到optimization.minimizer里面,效果一樣的

new CssMinimizerPlugin(),

// 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了

new TerserPlugin({

parallel: threads // 開(kāi)啟多進(jìn)程

}),

// 壓縮圖片

new ImageMinimizerPlugin({

minimizer: {

implementation: ImageMinimizerPlugin.imageminGenerate,

options: {

plugins: [

["gifsicle", { interlaced: true }],

["jpegtran", { progressive: true }],

["optipng", { optimizationLevel: 5 }],

[

"svgo",

{

plugins: [

"preset-default",

"prefixIds",

{

name: "sortAttrs",

params: {

xmlnsOrder: "alphabetical",

},

},

],

},

],

],

},

},

}),

],

},

// 開(kāi)發(fā)服務(wù)器

devServer: {

host: "localhost", // 啟動(dòng)服務(wù)器域名

port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

open: true, // 是否自動(dòng)打開(kāi)瀏覽器

hot: true, // 開(kāi)啟HMR功能(只能用于開(kāi)發(fā)環(huán)境,生產(chǎn)環(huán)境不需要了)

},

mode: 'development'

}

優(yōu)化代碼運(yùn)行性能

Code Split

介紹

打包代碼時(shí)會(huì)將所有 js 文件打包到一個(gè)文件中,體積太大了。我們?nèi)绻灰秩臼醉?yè),就應(yīng)該只加載首頁(yè)的 js 文件,其他文件不應(yīng)該加載。

所以我們需要將打包生成的文件進(jìn)行代碼分割,生成多個(gè) js 文件,渲染哪個(gè)頁(yè)面就只加載某個(gè) js 文件,這樣加載的資源就少,速度就更快。

代碼分割(Code Split)主要做了兩件事:

分割文件:將打包生成的文件進(jìn)行分割,生成多個(gè) js 文件。

按需加載:需要哪個(gè)文件就加載哪個(gè)文件。

使用

單頁(yè)面配置

如果我們使用了動(dòng)態(tài)導(dǎo)入的語(yǔ)法,那么在eslint里面需要支持一下 下載

npm i eslint-plugin-import -D

配置

// .eslintrc.js

module.exports = {

// 繼承 Eslint 規(guī)則

extends: ["eslint:recommended"],

env: {

node: true, // 啟用node中全局變量

browser: true, // 啟用瀏覽器中全局變量

},

plugins: ["import"], // 解決動(dòng)態(tài)導(dǎo)入import語(yǔ)法報(bào)錯(cuò)問(wèn)題 --> 實(shí)際使用eslint-plugin-import的規(guī)則解決的

parserOptions: {

ecmaVersion: 6,

sourceType: "module",

},

rules: {

"no-var": 2, // 不能使用 var 定義變量

},

};

Code Split統(tǒng)一命名

我們的配置中對(duì)于輸出文件的配置有很多,比如入口文件,圖片文件,字體圖標(biāo)文件、css文件、動(dòng)態(tài)引入文件等等,我們希望統(tǒng)一寫在一個(gè)地方 配置 這里以生產(chǎn)模式為例子

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

// cpu核數(shù)

const threads = os.cpus().length;

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

// 需要一個(gè)絕對(duì)路徑

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。

// 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的)

filename: "static/js/[name].js", // 入口文件打包輸出資源命名方式

chunkFilename: "static/js/[name].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash)

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

// generator: {

// // 將圖片文件輸出到 static/imgs 目錄中

// // 將圖片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件擴(kuò)展名

// // [query]: 添加之前的query參數(shù)

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 開(kāi)啟多進(jìn)程

options: {

workers: threads, // 數(shù)量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 開(kāi)啟多進(jìn)程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/[name].css",

chunkFilename: "static/css/[name].chunk.css",

}),

// new CssMinimizerPlugin()

],

// 推薦將壓縮操作放在這里

optimization: {

minimize: true,

minimizer: [

// css壓縮也可以寫到optimization.minimizer里面,效果一樣的

new CssMinimizerPlugin(),

// 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了

new TerserPlugin({

parallel: threads // 開(kāi)啟多進(jìn)程

}),

// 壓縮圖片

new ImageMinimizerPlugin({

minimizer: {

implementation: ImageMinimizerPlugin.imageminGenerate,

options: {

plugins: [

["gifsicle", { interlaced: true }],

["jpegtran", { progressive: true }],

["optipng", { optimizationLevel: 5 }],

[

"svgo",

{

plugins: [

"preset-default",

"prefixIds",

{

name: "sortAttrs",

params: {

xmlnsOrder: "alphabetical",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

}

},

// 開(kāi)發(fā)服務(wù)器

// devServer: {

// host: "localhost", // 啟動(dòng)服務(wù)器域名

// port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

// open: true, // 是否自動(dòng)打開(kāi)瀏覽器

// },

mode: 'production'

}

Preload / Prefetch 預(yù)處理

介紹

我們前面已經(jīng)做了代碼分割,同時(shí)會(huì)使用 import 動(dòng)態(tài)導(dǎo)入語(yǔ)法來(lái)進(jìn)行代碼按需加載(我們也叫懶加載,比如路由懶加載就是這樣實(shí)現(xiàn)的)。

但是加載速度還不夠好,比如:是用戶點(diǎn)擊按鈕時(shí)才加載這個(gè)資源的,如果資源體積很大,那么用戶會(huì)感覺(jué)到明顯卡頓效果。

我們想在瀏覽器空閑時(shí)間,加載后續(xù)需要使用的資源。我們就需要用上 Preload 或 Prefetch 技術(shù)

Preload:告訴瀏覽器立即加載資源。

Prefetch:告訴瀏覽器在空閑時(shí)才開(kāi)始加載資源。(開(kāi)發(fā)中這個(gè)用的最多)

它們共同點(diǎn):

都只會(huì)加載資源,并不執(zhí)行。

都有緩存。

它們區(qū)別:

Preload加載優(yōu)先級(jí)高,Prefetch加載優(yōu)先級(jí)低。

Preload只能加載當(dāng)前頁(yè)面需要使用的資源,Prefetch可以加載當(dāng)前頁(yè)面資源,也可以加載下一個(gè)頁(yè)面需要使用的資源。

總結(jié):

當(dāng)前頁(yè)面優(yōu)先級(jí)高的資源用 Preload 加載。

下一個(gè)頁(yè)面需要使用的資源用 Prefetch 加載。

它們的問(wèn)題:兼容性較差。

我們可以去 Can I Use 網(wǎng)站查詢 API 的兼容性問(wèn)題。

Preload 相對(duì)于 Prefetch 兼容性好一點(diǎn)。

使用

下載

npm i @vue/preload-webpack-plugin -D

配置 以生產(chǎn)模式為例子

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// cpu核數(shù)

const threads = os.cpus().length;

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

// 需要一個(gè)絕對(duì)路徑

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。

// 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的)

filename: "static/js/[name].js", // 入口文件打包輸出資源命名方式

chunkFilename: "static/js/[name].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash)

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

// generator: {

// // 將圖片文件輸出到 static/imgs 目錄中

// // 將圖片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件擴(kuò)展名

// // [query]: 添加之前的query參數(shù)

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 開(kāi)啟多進(jìn)程

options: {

workers: threads, // 數(shù)量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 開(kāi)啟多進(jìn)程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/[name].css",

chunkFilename: "static/css/[name].chunk.css",

}),

// new CssMinimizerPlugin()

new PreloadWebpackPlugin({

rel: "preload", // preload兼容性更好

as: "script",

// rel: 'prefetch' // prefetch兼容性更差

}),

],

// 推薦將壓縮操作放在這里

optimization: {

minimize: true,

minimizer: [

// css壓縮也可以寫到optimization.minimizer里面,效果一樣的

new CssMinimizerPlugin(),

// 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了

new TerserPlugin({

parallel: threads // 開(kāi)啟多進(jìn)程

}),

// 壓縮圖片

new ImageMinimizerPlugin({

minimizer: {

implementation: ImageMinimizerPlugin.imageminGenerate,

options: {

plugins: [

["gifsicle", { interlaced: true }],

["jpegtran", { progressive: true }],

["optipng", { optimizationLevel: 5 }],

[

"svgo",

{

plugins: [

"preset-default",

"prefixIds",

{

name: "sortAttrs",

params: {

xmlnsOrder: "alphabetical",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

}

},

// 開(kāi)發(fā)服務(wù)器

// devServer: {

// host: "localhost", // 啟動(dòng)服務(wù)器域名

// port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

// open: true, // 是否自動(dòng)打開(kāi)瀏覽器

// },

mode: 'production'

}

Network Cache

介紹

將來(lái)開(kāi)發(fā)時(shí)我們對(duì)靜態(tài)資源會(huì)使用緩存來(lái)優(yōu)化,這樣瀏覽器第二次請(qǐng)求資源就能讀取緩存了,速度很快。

但是這樣的話就會(huì)有一個(gè)問(wèn)題, 因?yàn)榍昂筝敵龅奈募且粯拥?,都?main.js,一旦將來(lái)發(fā)布新版本,因?yàn)槲募麤](méi)有變化導(dǎo)致瀏覽器會(huì)直接讀取緩存,不會(huì)加載新資源,項(xiàng)目也就沒(méi)法更新了。

所以我們從文件名入手,確保更新前后文件名不一樣,這樣就可以做緩存了。

它們都會(huì)生成一個(gè)唯一的 hash 值。

fullhash(webpack4 是 hash)

每次修改任何一個(gè)文件,所有文件名的 hash 至都將改變。所以一旦修改了任何一個(gè)文件,整個(gè)項(xiàng)目的文件緩存都將失效。

chunkhash

根據(jù)不同的入口文件(Entry)進(jìn)行依賴文件解析、構(gòu)建對(duì)應(yīng)的 chunk,生成對(duì)應(yīng)的哈希值。我們 js 和 css 是同一個(gè)引入,會(huì)共享一個(gè) hash 值。

contenthash

根據(jù)文件內(nèi)容生成 hash 值,只有文件內(nèi)容變化了,hash 值才會(huì)變化。所有文件 hash 值是獨(dú)享且不同的。

存在問(wèn)題與解決方案

問(wèn)題: 當(dāng)我們修改 math.js 文件再重新打包的時(shí)候,因?yàn)?contenthash 原因,math.js 文件 hash 值發(fā)生了變化(這是正常的)。

但是 main.js 文件的 hash 值也發(fā)生了變化,這會(huì)導(dǎo)致 main.js 的緩存失效。明明我們只修改 math.js, 為什么 main.js 也會(huì)變身變化呢?

原因:

更新前:math.xxx.js, main.js 引用的 math.xxx.js 更新后:math.yyy.js, main.js 引用的 math.yyy.js, 文件名發(fā)生了變化,間接導(dǎo)致 main.js 也發(fā)生了變化 解決:

將 hash 值單獨(dú)保管在一個(gè) runtime 文件中。

我們最終輸出三個(gè)文件:main、math、runtime。當(dāng) math 文件發(fā)送變化,變化的是 math 和 runtime 文件,main 不變。

runtime 文件只保存文件的 hash 值和它們與文件關(guān)系,整個(gè)文件體積就比較小,所以變化重新請(qǐng)求的代價(jià)也小。

使用

// 提取runtime文件

runtimeChunk: {

name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名規(guī)則

},

以生產(chǎn)模式為例子

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// cpu核數(shù)

const threads = os.cpus().length;

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

// 需要一個(gè)絕對(duì)路徑

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。

// 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的)

// [contenthash:8]使用contenthash,取8位長(zhǎng)度

filename: "static/js/[name].[contenthash:8].js", // 入口文件打包輸出資源命名方式

chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash)

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

// generator: {

// // 將圖片文件輸出到 static/imgs 目錄中

// // 將圖片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件擴(kuò)展名

// // [query]: 添加之前的query參數(shù)

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 開(kāi)啟多進(jìn)程

options: {

workers: threads, // 數(shù)量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 開(kāi)啟多進(jìn)程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/[name].[contenthash:8].css",

chunkFilename: "static/css/[name].[contenthash:8].chunk.css",

}),

// new CssMinimizerPlugin()

new PreloadWebpackPlugin({

rel: "preload", // preload兼容性更好

as: "script",

// rel: 'prefetch' // prefetch兼容性更差

}),

],

// 推薦將壓縮操作放在這里

optimization: {

minimize: true,

minimizer: [

// css壓縮也可以寫到optimization.minimizer里面,效果一樣的

new CssMinimizerPlugin(),

// 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了

new TerserPlugin({

parallel: threads // 開(kāi)啟多進(jìn)程

}),

// 壓縮圖片

new ImageMinimizerPlugin({

minimizer: {

implementation: ImageMinimizerPlugin.imageminGenerate,

options: {

plugins: [

["gifsicle", { interlaced: true }],

["jpegtran", { progressive: true }],

["optipng", { optimizationLevel: 5 }],

[

"svgo",

{

plugins: [

"preset-default",

"prefixIds",

{

name: "sortAttrs",

params: {

xmlnsOrder: "alphabetical",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

},

// 提取runtime文件

runtimeChunk: {

name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名規(guī)則

},

},

// 開(kāi)發(fā)服務(wù)器

// devServer: {

// host: "localhost", // 啟動(dòng)服務(wù)器域名

// port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

// open: true, // 是否自動(dòng)打開(kāi)瀏覽器

// },

mode: 'production'

}

Core-js徹底解決js的兼容問(wèn)題

介紹

過(guò)去我們使用 babel 對(duì) js 代碼進(jìn)行了兼容性處理,其中使用@babel/preset-env 智能預(yù)設(shè)來(lái)處理兼容性問(wèn)題。

它能將 ES6 的一些語(yǔ)法進(jìn)行編譯轉(zhuǎn)換,比如箭頭函數(shù)、點(diǎn)點(diǎn)點(diǎn)運(yùn)算符等。但是如果是 async 函數(shù)、promise 對(duì)象、數(shù)組的一些方法(includes)等,它沒(méi)辦法處理。

所以此時(shí)我們 js 代碼仍然存在兼容性問(wèn)題,一旦遇到低版本瀏覽器會(huì)直接報(bào)錯(cuò)。所以我們想要將 js 兼容性問(wèn)題徹底解決

core-js 是專門用來(lái)做 ES6 以及以上 API 的 polyfill。

polyfill翻譯過(guò)來(lái)叫做墊片/補(bǔ)丁。就是用社區(qū)上提供的一段代碼,讓我們?cè)诓患嫒菽承┬绿匦缘臑g覽器上,使用該新特性。

使用

下載

npm i core-js

三種使用方式

手動(dòng)全部引入

import "core-js";

這樣引入會(huì)將所有兼容性代碼全部引入,體積太大了。我們只想引入 promise 的 polyfill。

手動(dòng)按需引入

import "core-js/es/promise";

只引入打包 promise 的 polyfill,打包體積更小。但是將來(lái)如果還想使用其他語(yǔ)法,我需要手動(dòng)引入庫(kù)很麻煩。

自動(dòng)按需引入 修改babel.config.js的智能預(yù)設(shè),幫助我們進(jìn)行core.js的按需加載

// babel.config.js

module.exports = {

// 智能預(yù)設(shè):能夠編譯ES6語(yǔ)法

presets: [

[

"@babel/preset-env",

// 按需加載core-js的polyfill

{ useBuiltIns: "usage", corejs: { version: "3", proposals: true } },

],

],

};

此時(shí)就會(huì)自動(dòng)根據(jù)我們代碼中使用的語(yǔ)法,來(lái)按需加載相應(yīng)的 polyfill 了。

PWA

介紹

開(kāi)發(fā) Web App 項(xiàng)目,項(xiàng)目一旦處于網(wǎng)絡(luò)離線情況,就沒(méi)法訪問(wèn)了。

我們希望給項(xiàng)目提供離線體驗(yàn)。

漸進(jìn)式網(wǎng)絡(luò)應(yīng)用程序(progressive web application - PWA):

是一種可以提供類似于 native app(原生應(yīng)用程序) 體驗(yàn)的 Web App 的技術(shù)。

其中最重要的是,在 離線(offline) 時(shí)應(yīng)用程序能夠繼續(xù)運(yùn)行功能。

內(nèi)部通過(guò) Service Workers 技術(shù)實(shí)現(xiàn)的。

使用

下載

npm i workbox-webpack-plugin -D

配置

// path 為 Node.js的核心模塊,專門用來(lái)處理文件路徑

const path = require('path')

const os = require("os");

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

const HtmlWebpackPlugin = require("html-webpack-plugin");

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const TerserPlugin = require("terser-webpack-plugin");

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

const WorkboxPlugin = require("workbox-webpack-plugin");

// cpu核數(shù)

const threads = os.cpus().length;

// 獲取處理樣式的Loaders

const getStyleLoaders = (preProcessor) => {

return [

MiniCssExtractPlugin.loader,

"css-loader",

{

loader: "postcss-loader",

options: {

postcssOptions: {

plugins: [

"postcss-preset-env", // 能解決大多數(shù)樣式兼容性問(wèn)題

],

},

},

},

preProcessor,

].filter(Boolean);

};

module.exports = {

// 入口

entry: {

// 需要一個(gè)相對(duì)路徑

index: './src/index.js'

},

// 輸出

output: {

// 需要一個(gè)絕對(duì)路徑

path: path.resolve(__dirname, '../dist'),

clean: true,

// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名無(wú)關(guān)。

// 為什么需要這樣命名呢?如果還是之前寫法main.js,那么打包生成兩個(gè)js文件都會(huì)叫做main.js會(huì)發(fā)生覆蓋。(實(shí)際上會(huì)直接報(bào)錯(cuò)的)

// [contenthash:8]使用contenthash,取8位長(zhǎng)度

filename: "static/js/[name].[contenthash:8].js", // 入口文件打包輸出資源命名方式

chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 動(dòng)態(tài)導(dǎo)入輸出資源命名方式

assetModuleFilename: "static/media/[name].[hash][ext]", // 圖片、字體等資源命名方式(注意用hash)

},

// Performance 這些選項(xiàng)可以控制 webpack 如何通知「資源(asset)和入口起點(diǎn)超過(guò)指定文件限制」。

performance: {

hints: false

},

devtool: "source-map",

// 解析器

module: {

rules: [

{

oneOf: [

{

test: /\.css$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders(),

},

{

// 正則匹配所有已.less文件結(jié)尾的文件

test: /\.less$/,

// loader的執(zhí)行順序是從右往左的,所以這里先寫style-loader,再寫css-loader

// 如果只使用一個(gè)loader的話,可以使用loader屬性代替use,如下

// loader:"style-loader"

use: getStyleLoaders("less-loader"),

},

{

test: /\.s[ac]ss$/,

use: getStyleLoaders("sass-loader"),

},

{

test: /\.styl$/,

use: getStyleLoaders("stylus-loader"),

},

{

test: /\.(png|jpe?g|gif|webp)$/,

type: "asset",

// 類似于 module.generator,你可以用 module.parser 在一個(gè)地方配置所有解析器的選項(xiàng)。

parser: {

// 如果一個(gè)模塊源碼大小小于 maxSize,那么模塊會(huì)被作為一個(gè) Base64 編碼的字符串注入到包中, 否則模塊文件會(huì)被生成到輸出的目標(biāo)目錄中。

dataUrlCondition: {

maxSize: 10 * 1024 // 小于10kb的圖片會(huì)被base64處理

}

},

// 可以使用 module.generator 在一個(gè)地方配置所有生成器的選項(xiàng)

// generator: {

// // 將圖片文件輸出到 static/imgs 目錄中

// // 將圖片文件命名 [hash:8][ext][query]

// // [hash:8]: hash值取8位

// // [ext]: 使用之前的文件擴(kuò)展名

// // [query]: 添加之前的query參數(shù)

// filename: "static/imgs/[hash:8][ext][query]",

// },

},

{

test: /\.(ttf|woff2?|svg)$/,

// 發(fā)送一個(gè)單獨(dú)的文件并導(dǎo)出 URL

type: "asset/resource",

// generator: {

// filename: "static/media/[hash:8][ext][query]",

// },

},

{

test: /\.js$/,

// exclude: /node_modules/, // 排除node_modules代碼不編譯

include: path.resolve(__dirname, "../src"), // 也可以用包含

use: [

{

loader: "thread-loader", // 開(kāi)啟多進(jìn)程

options: {

workers: threads, // 數(shù)量

},

},

{

loader: "babel-loader",

options: {

cacheDirectory: true, // 開(kāi)啟babel編譯緩存

cacheCompression: false, // 緩存文件不要壓縮

plugins: ["@babel/plugin-transform-runtime"], // 減少代碼體積

},

},

],

},

]

}

]

},

plugins: [

new ESLintWebpackPlugin({

// 指定檢查文件的根目錄

context: path.resolve(__dirname, "../src"),

exclude: "node_modules", // 默認(rèn)值

cache: true, // 開(kāi)啟緩存

// 緩存目錄

cacheLocation: path.resolve(

__dirname,

"../node_modules/.cache/.eslintcache"

),

threads, // 開(kāi)啟多進(jìn)程

}),

new HtmlWebpackPlugin({

// 以 public/index.html 為模板創(chuàng)建文件

// 新的html文件有兩個(gè)特點(diǎn):1. 內(nèi)容和源文件一致 2. 自動(dòng)引入打包生成的js等資源

template: path.resolve(__dirname, "../public/index.html"),

}),

// 提取css成單獨(dú)文件

new MiniCssExtractPlugin({

// 定義輸出文件名和目錄

filename: "static/css/[name].[contenthash:8].css",

chunkFilename: "static/css/[name].[contenthash:8].chunk.css",

}),

// new CssMinimizerPlugin()

new PreloadWebpackPlugin({

rel: "preload", // preload兼容性更好

as: "script",

// rel: 'prefetch' // prefetch兼容性更差

}),

new WorkboxPlugin.GenerateSW({

// 這些選項(xiàng)幫助快速啟用 ServiceWorkers

// 不允許遺留任何“舊的” ServiceWorkers

clientsClaim: true,

skipWaiting: true,

}),

],

// 推薦將壓縮操作放在這里

optimization: {

minimize: true,

minimizer: [

// css壓縮也可以寫到optimization.minimizer里面,效果一樣的

new CssMinimizerPlugin(),

// 當(dāng)生產(chǎn)模式會(huì)默認(rèn)開(kāi)啟TerserPlugin,但是我們需要進(jìn)行其他配置,就要重新寫了

new TerserPlugin({

parallel: threads // 開(kāi)啟多進(jìn)程

}),

// 壓縮圖片

new ImageMinimizerPlugin({

minimizer: {

implementation: ImageMinimizerPlugin.imageminGenerate,

options: {

plugins: [

["gifsicle", { interlaced: true }],

["jpegtran", { progressive: true }],

["optipng", { optimizationLevel: 5 }],

[

"svgo",

{

plugins: [

"preset-default",

"prefixIds",

{

name: "sortAttrs",

params: {

xmlnsOrder: "alphabetical",

},

},

],

},

],

],

},

},

}),

],

splitChunks: {

chunks: "all"

},

// 提取runtime文件

runtimeChunk: {

name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名規(guī)則

},

},

// 開(kāi)發(fā)服務(wù)器

// devServer: {

// host: "localhost", // 啟動(dòng)服務(wù)器域名

// port: "3000", // 啟動(dòng)服務(wù)器端口號(hào)

// open: true, // 是否自動(dòng)打開(kāi)瀏覽器

// },

mode: 'production'

}

問(wèn)題與解決

此時(shí)如果直接通過(guò) VSCode 訪問(wèn)打包后頁(yè)面,在瀏覽器控制臺(tái)會(huì)發(fā)現(xiàn) SW registration failed。

因?yàn)槲覀兇蜷_(kāi)的訪問(wèn)路徑是:http://127.0.0.1:5500/dist/index.html。此時(shí)頁(yè)面會(huì)去請(qǐng)求 service-worker.js 文件,請(qǐng)求路徑是:http://127.0.0.1:5500/service-worker.js,這樣找不到會(huì) 404。

實(shí)際 service-worker.js 文件路徑是:http://127.0.0.1:5500/dist/service-worker.js。

解決

下載包 npm i serve -g

serve 也是用來(lái)啟動(dòng)開(kāi)發(fā)服務(wù)器來(lái)部署代碼查看效果的。

運(yùn)行指令 serve dist

此時(shí)通過(guò) serve 啟動(dòng)的服務(wù)器我們 service-worker 就能注冊(cè)成功了。

高級(jí)總結(jié)

我們從 4 個(gè)角度對(duì) webpack 和代碼進(jìn)行了優(yōu)化:

提升開(kāi)發(fā)體驗(yàn)

使用 Source Map 讓開(kāi)發(fā)或上線時(shí)代碼報(bào)錯(cuò)能有更加準(zhǔn)確的錯(cuò)誤提示。提升 webpack 提升打包構(gòu)建速度使用 HotModuleReplacement 讓開(kāi)發(fā)時(shí)只重新編譯打包更新變化了的代碼,不變的代碼使用緩存,從而使更新速度更快。使用 OneOf 讓資源文件一旦被某個(gè) loader 處理了,就不會(huì)繼續(xù)遍歷了,打包速度更快。使用 Include/Exclude 排除或只檢測(cè)某些文件,處理的文件更少,速度更快。使用 Cache 對(duì) eslint 和 babel 處理的結(jié)果進(jìn)行緩存,讓第二次打包速度更快。使用 Thead 多進(jìn)程處理 eslint 和 babel 任務(wù),速度更快。(需要注意的是,進(jìn)程啟動(dòng)通信都有開(kāi)銷的,要在比較多代碼處理時(shí)使用才有效果)

減少代碼體積

使用 Tree Shaking 剔除了沒(méi)有使用的多余代碼,讓代碼體積更小。使用 @babel/plugin-transform-runtime 插件對(duì) babel 進(jìn)行處理,讓輔助代碼從中引入,而不是每個(gè)文件都生成輔助代碼,從而體積更小。使用 Image Minimizer 對(duì)項(xiàng)目中圖片進(jìn)行壓縮,體積更小,請(qǐng)求速度更快。(需要注意的是,如果項(xiàng)目中圖片都是在線鏈接,那么就不需要了。本地項(xiàng)目靜態(tài)圖片才需要進(jìn)行壓縮。)

優(yōu)化代碼運(yùn)行性能

使用 Code Split 對(duì)代碼進(jìn)行分割成多個(gè) js 文件,從而使單個(gè)文件體積更小,并行加載 js 速度更快。并通過(guò) import 動(dòng)態(tài)導(dǎo)入語(yǔ)法進(jìn)行按需加載,從而達(dá)到需要使用時(shí)才加載該資源,不用時(shí)不加載資源。使用 Preload / Prefetch 對(duì)代碼進(jìn)行提前加載,等未來(lái)需要使用時(shí)就能直接使用,從而用戶體驗(yàn)更好。使用 Network Cache 能對(duì)輸出資源文件進(jìn)行更好的命名,將來(lái)好做緩存,從而用戶體驗(yàn)更好。使用 Core-js 對(duì) js 進(jìn)行兼容性處理,讓我們代碼能運(yùn)行在低版本瀏覽器。使用 PWA 能讓代碼離線也能訪問(wèn),從而提升用戶體驗(yàn)。

如果你學(xué)習(xí)到這了,那么恭喜你,你已經(jīng)可以搞定面試官了?。?!


該文章在 2025/4/30 15:36:46 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved