Webpack 4 Tree Shaking 終極優化指南

来源:https://www.cnblogs.com/lzkwin/archive/2019/11/17/11878509.html
-Advertisement-
Play Games

幾個月前,我的任務是將我們組的 Vue.js 項目構建配置升級到 Webpack 4。我們的主要目標之一是利用 tree shaking 的優勢,即 Webpack 去掉了實際上並沒有使用的代碼來減少包的大小。現在,tree shaking 的好處將根據你的代碼庫而有所不同。由於我們的幾個架構決策, ...


幾個月前,我的任務是將我們組的 Vue.js 項目構建配置升級到 Webpack 4。我們的主要目標之一是利用 tree-shaking 的優勢,即 Webpack 去掉了實際上並沒有使用的代碼來減少包的大小。現在,tree-shaking 的好處將根據你的代碼庫而有所不同。由於我們的幾個架構決策,我們從公司內部的其他庫中提取了大量代碼,而我們只使用了其中的一小部分。

我寫這篇文章是因為恰當地優化 Webpack 並不簡單。一開始我以為這是一種簡單的魔法,但後來我花了一個月的時間在網上搜索我遇到的一系列問題的答案。我希望通過這篇文章,其他人會更容易地處理類似問題。

先說好處

在討論技術細節之前,讓我先總結一下好處。不同的應用程式將看到不同程度的好處。主要的決定因素是應用程式中死代碼的數量。如果你沒有多少死代碼,那麼你就看不到 tree-shaking 的多少好處。我們項目里有很多死代碼。

在我們部門,最大的問題是共用庫的數量。從簡單的自定義組件庫,到企業標準組件庫,再到莫名其妙地塞到一個庫中的大量代碼。很多都是技術債務,但一個大問題是我們所有的應用程式都在導入所有這些庫,而實際上每個應用程式都只需要其中的一小部分

總的來說,一旦實現了 tree-shaking,我們的應用程式就會根據應用程式的不同,縮減率從25%到75%。平均縮減率為52%,主要是由這些龐大的共用庫驅動的,它們是小型應用程式中的主要代碼。

同樣,具體情況會有所不同,但是如果你覺得你打的包中可能有很多不需要的代碼,這就是如何消除它們的方法。

沒有示例代碼倉庫

對不住了各位老鐵,我做的項目是公司的財產,所以我不能分享代碼到 GitHub 倉庫了。但是,我將在本文中提供簡化的代碼示例來說明我的觀點。

因此,廢話少說,讓我們來看看如何編寫可實現 tree-shaking 的最佳 webpack 4 配置。

什麼是死代碼

很簡單:就是 Webpack 沒看到你使用的代碼。Webpack 跟蹤整個應用程式的 import/export 語句,因此,如果它看到導入的東西最終沒有被使用,它會認為那是“死代碼”,並會對其進行 tree-shaking 。

死代碼並不總是那麼明確的。下麵是一些死代碼和“活”代碼的例子,希望能讓你更明白。請記住,在某些情況下,Webpack 會將某些東西視為死代碼,儘管它實際上並不是。請參閱《副作用》一節,瞭解如何處理。

// 導入並賦值給 JavaScript 對象,然後在下麵的代碼中被用到
// 這會被看作“活”代碼,不會做 tree-shaking
import Stuff from './stuff';
doSomething(Stuff);
// 導入並賦值給 JavaScript 對象,但在接下來的代碼里沒有用到
// 這就會被當做“死”代碼,會被 tree-shaking
import Stuff from './stuff';
doSomething();
// 導入但沒有賦值給 JavaScript 對象,也沒有在代碼里用到
// 這會被當做“死”代碼,會被 tree-shaking
import './stuff';
doSomething();
// 導入整個庫,但是沒有賦值給 JavaScript 對象,也沒有在代碼里用到
// 非常奇怪,這竟然被當做“活”代碼,因為 Webpack 對庫的導入和本地代碼導入的處理方式不同。
import 'my-lib';
doSomething();

用支持 tree-shaking 的方式寫 import

在編寫支持 tree-shaking 的代碼時,導入方式非常重要。你應該避免將整個庫導入到單個 JavaScript 對象中。當你這樣做時,你是在告訴 Webpack 你需要整個庫, Webpack 就不會搖它。

以流行的庫 Lodash 為例。一次導入整個庫是一個很大的錯誤,但是導入單個的模塊要好得多。當然,Lodash 還需要其他的步驟來做 tree-shaking,但這是個很好的起點。

// 全部導入 (不支持 tree-shaking)
import _ from 'lodash';
// 具名導入(支持 tree-shaking)
import { debounce } from 'lodash';
// 直接導入具體的模塊 (支持 tree-shaking)
import debounce from 'lodash/lib/debounce';

基本的 Webpack 配置

使用 Webpack 進行 tree-shaking 的第一步是編寫 Webpack 配置文件。你可以對你的 webpack 做很多自定義配置,但是如果你想要對代碼進行 tree-shaking,就需要以下幾項。

首先,你必須處於生產模式。Webpack 只有在壓縮代碼的時候會 tree-shaking,而這隻會發生在生產模式中。

其次,必須將優化選項 “usedExports” 設置為true。這意味著 Webpack 將識別出它認為沒有被使用的代碼,併在最初的打包步驟中給它做標記。

最後,你需要使用一個支持刪除死代碼的壓縮器。這種壓縮器將識別出 Webpack 是如何標記它認為沒有被使用的代碼,並將其剝離。TerserPlugin 支持這個功能,推薦使用。

下麵是 Webpack 開啟 tree-shaking 的基本配置:

// Base Webpack Config for Tree Shaking
const config = {
 mode: 'production',
 optimization: {
  usedExports: true,
  minimizer: [
   new TerserPlugin({...})
  ]
 }
};

有什麼副作用

僅僅因為 Webpack 看不到一段正在使用的代碼,並不意味著它可以安全地進行 tree-shaking。有些模塊導入,只要被引入,就會對應用程式產生重要的影響。一個很好的例子就是全局樣式表,或者設置全局配置的JavaScript 文件。

Webpack 認為這樣的文件有“副作用”。具有副作用的文件不應該做 tree-shaking,因為這將破壞整個應用程式。Webpack 的設計者清楚地認識到不知道哪些文件有副作用的情況下打包代碼的風險,因此預設地將所有代碼視為有副作用。這可以保護你免於刪除必要的文件,但這意味著 Webpack 的預設行為實際上是不進行 tree-shaking。

幸運的是,我們可以配置我們的項目,告訴 Webpack 它是沒有副作用的,可以進行 tree-shaking。

如何告訴 Webpack 你的代碼無副作用

package.json 有一個特殊的屬性 sideEffects,就是為此而存在的。它有三個可能的值:

true 是預設值,如果不指定其他值的話。這意味著所有的文件都有副作用,也就是沒有一個文件可以 tree-shaking。

false 告訴 Webpack 沒有文件有副作用,所有文件都可以 tree-shaking。

第三個值 […] 是文件路徑數組。它告訴 webpack,除了數組中包含的文件外,你的任何文件都沒有副作用。因此,除了指定的文件之外,其他文件都可以安全地進行 tree-shaking。

每個項目都必須將 sideEffects 屬性設置為 false 或文件路徑數組。在我公司的工作中,我們的基本應用程式和我提到的所有共用庫都需要正確配置 sideEffects 標誌。

下麵是 sideEffects 標誌的一些代碼示例。儘管有 JavaScript 註釋,但這是 JSON 代碼:

// 所有文件都有副作用,全都不可 tree-shaking
{
 "sideEffects": true
}
// 沒有文件有副作用,全都可以 tree-shaking
{
 "sideEffects": false
}
// 只有這些文件有副作用,所有其他文件都可以 tree-shaking,但會保留這些文件
{
 "sideEffects": [
  "./src/file1.js",
  "./src/file2.js"
 ]
}

全局 CSS 與副作用

首先,讓我們在這個上下文中定義全局 CSS。全局 CSS 是直接導入到 JavaScript 文件中的樣式表(可以是CSS、SCSS等)。它沒有被轉換成 CSS 模塊或任何類似的東西。基本上,import 語句是這樣的:

// 導入全局 CSS
import './MyStylesheet.css';

因此,如果你做了上面提到的副作用更改,那麼在運行 webpack 構建時,你將立即註意到一個棘手的問題。以上述方式導入的任何樣式表現在都將從輸出中刪除。這是因為這樣的導入被 webpack 視為死代碼,並被刪除。

幸運的是,有一個簡單的解決方案可以解決這個問題。Webpack 使用它的模塊規則系統來控制各種類型文件的載入。每種文件類型的每個規則都有自己的 sideEffects 標誌。這會覆蓋之前為匹配規則的文件設置的所有 sideEffects 標誌。

所以,為了保留全局 CSS 文件,我們只需要設置這個特殊的 sideEffects 標誌為 true,就像這樣:

// 全局 CSS 副作用規則相關的 Webpack 配置
const config = {
 module: {
  rules: [
   {
    test: /regex/,
    use: [loaders],
    sideEffects: true
   }
  ]
 } 
};

Webpack 的所有模塊規則上都有這個屬性。處理全局樣式表的規則必須用上它,包括但不限於 CSS/SCSS/LESS/等等。

什麼是模塊,模塊為什麼重要

現在我們開始進入秘境。錶面上看,編譯出正確的模塊類型似乎是一個簡單的步驟,但是正如下麵幾節將要解釋的,這是一個會導致許多複雜問題的領域。這是我花了很長時間才弄明白的部分。

首先,我們需要瞭解一下模塊。多年來,JavaScript 已經發展出了在文件之間以“模塊”的形式有效導入/導出代碼的能力。有許多不同的 JavaScript 模塊標準已經存在了多年,但是為了本文的目的,我們將重點關註兩個標準。一個是 “commonjs”,另一個是 “es2015”。下麵是它們的代碼形式:

// Commonjs
const stuff = require('./stuff');
module.exports = stuff;

// es2015 
import stuff from './stuff';
export default stuff;

預設情況下,Babel 假定我們使用 es2015 模塊編寫代碼,並轉換 JavaScript 代碼以使用 commonjs 模塊。這樣做是為了與伺服器端 JavaScript 庫的廣泛相容性,這些 JavaScript 庫通常構建在 NodeJS 之上(NodeJS 只支持 commonjs 模塊)。但是,Webpack 不支持使用 commonjs 模塊來完成 tree-shaking。

現在,有一些插件(如 common-shake-plugin)聲稱可以讓 Webpack 有能力對 commonjs 模塊進行 tree-shaking,但根據我的經驗,這些插件要麼不起作用,要麼在 es2015 模塊上運行時,對 tree-shaking 的影響微乎其微。我不推薦這些插件。

因此,為了進行 tree-shaking,我們需要將代碼編譯到 es2015 模塊。

es2015 模塊 Babel 配置

據我所知,Babel 不支持將其他模塊系統編譯成 es2015 模塊。但是,如果你是前端開發人員,那麼你可能已經在使用 es2015 模塊編寫代碼了,因為這是全面推薦的方法。

因此,為了讓我們編譯的代碼使用 es2015 模塊,我們需要做的就是告訴 babel 不要管它們。為了實現這一點,我們只需將以下內容添加到我們的 babel.config.js 中(在本文中,你會看到我更喜歡JavaScript 配置而不是 JSON 配置):

// es2015 模塊的基本 Babel 配置
const config = {
 presets: [
  [
   '[@babel/preset-env](http://twitter.com/babel/preset-env)',
   {
    modules: false
   }
  ]
 ]
};

modules 設置為 false,就是告訴 babel 不要編譯模塊代碼。這會讓 Babel 保留我們現有的 es2015 import/export 語句。

劃重點:所有可需要 tree-shaking 的代碼必須以這種方式編譯。因此,如果你有要導入的庫,則必須將這些庫編譯為 es2015 模塊以便進行 tree-shaking 。如果它們被編譯為 commonjs,那麼它們就不能做 tree-shaking ,並且將會被打包進你的應用程式中。許多庫支持部分導入,lodash 就是一個很好的例子,它本身是 commonjs 模塊,但是它有一個 lodash-es 版本,用的是 es2015模塊。

此外,如果你在應用程式中使用內部庫,也必須使用 es2015 模塊編譯。為了減少應用程式包的大小,必須將所有這些內部庫修改為以這種方式編譯。

不好意思, Jest 罷工了

其他測試框架情況類似,我們用的是 Jest。

不管怎麼樣,如果你走到了這一步,你會發現 Jest 測試開始失敗了。你會像我當時一樣,看到日誌里出現各種奇怪的錯誤,慌的一批。別慌,我會帶你一步一步解決。

出現這個結果的原因很簡單:NodeJS。Jest 是基於 NodeJS 開發的,而 NodeJS 不支持 es2015 模塊。為此有一些方法可以配置 Node,但是在 jest 上行不通。因此,我們卡在這裡了:Webpack 需要 es2015 進行 tree shaking,但是 Jest 無法在這些模塊上執行測試。

就是為什麼我說進入了模塊系統的“秘境”。這是整個過程中耗費我最多時間來搞清楚的部分。建議你仔細閱讀這一節和後面幾節,因為我會給出解決方案。

解決方案有兩個主要部分。第一部分針對項目本身的代碼,也就是跑測試的代碼。這部分比較容易。第二部分針對庫代碼,也就是來自其他項目,被編譯成 es2015 模塊並引入到當前項目的代碼。這部分比較複雜。

解決項目本地 Jest 代碼

針對我們的問題,babel 有一個很有用的特性:環境選項。通過配置可以運行在不同環境。在這裡,開發和生產環境我們需要 es2015 模塊,而測試環境需要 commonjs 模塊。還好,Babel 配置起來非常容易:

// 分環境配置Babel 
const config = {
 env: {
  development: {
   presets: [
    [
     '[@babel/preset-env](http://twitter.com/babel/preset-env)',
     {
      modules: false
     }
    ]
   ]
  },
  production: {
   presets: [
    [
     '[@babel/preset-env](http://twitter.com/babel/preset-env)',
     {
      modules: false
     }
    ]
   ]
  },
  test: {
   presets: [
    [
     '[@babel/preset-env](http://twitter.com/babel/preset-env)',
     {
      modules: 'commonjs'
     }
    ]
   ],
   plugins: [
    'transform-es2015-modules-commonjs' // Not sure this is required, but I had added it anyway
   ]
  }
 }
};

設置好之後,所有的項目本地代碼能夠正常編譯,Jest 測試能運行了。但是,使用 es2015 模塊的第三方庫代碼依然不能運行。

解決 Jest 中的庫代碼

庫代碼運行出錯的原因非常明顯,看一眼node_modules 目錄就明白了。這裡的庫代碼用的是 es2015 模塊語法,為了進行 tree-shaking。這些庫已經採用這種方式編譯過了,因此當 Jest 在單元測試中試圖讀取這些代碼時,就炸了。註意到沒有,我們已經讓 Babel 在測試環境中啟用 commonjs 模塊了呀,為什麼對這些庫不起作用呢?這是因為,Jest (尤其是 babel-jest) 在跑測試之前編譯代碼的時候,預設忽略任何來自node_modules 的代碼。

這實際上是件好事。如果 Jest 需要重新編譯所有庫的話,將會大大增加測試處理時間。然而,雖然我們不想讓它重新編譯所有代碼,但我們希望它重新編譯使用 es2015 模塊的庫,這樣才能在單元測試里使用。

幸好,Jest 在它的配置中為我們提供瞭解決方案。我想說,這部分確實讓我想了很久,並且我感覺沒必要搞得這麼複雜,但這是我能想到的唯一解決方案。

配置 Jest 重新編譯庫代碼

// 重新編譯庫代碼的 Jest 配置 
const path = require('path');
const librariesToRecompile = [
 'Library1',
 'Library2'
].join('|');
const config = {
 transformIgnorePatterns: [
  `[\\\/]node_modules[\\\/](?!(${librariesToRecompile})).*$`
 ],
 transform: {
  '^.+\.jsx?$': path.resolve(__dirname, 'transformer.js')
 }
};

以上配置是 Jest 重新編譯你的庫所需要的。有兩個主要部分,我會一一解釋。

transformIgnorePatterns 是 Jest 配置的一個功能,它是一個正則字元串數組。任何匹配這些正則表達式的代碼,都不會被 babel-jest 重新編譯。預設是一個字元串“node_modules”。這就是為什麼Jest 不會重新編譯任何庫代碼。

當我們提供了自定義配置,就是告訴 Jest 重新編譯的時候如何忽略代碼。也就是為什麼你剛纔看到的變態的正則表達式有一個負向先行斷言在裡面,目的是為了匹配除了庫以外的所有代碼。換句話說,我們告訴 Jest 忽略 node_modules 中除了指定庫之外的所有代碼。

這又一次證明瞭 JavaScript 配置比 JSON 配置要好,因為我可以輕鬆地通過字元串操作,往正則表達式里插入庫名字的數組拼接。

第二個是 transform 配置,他指向一個自定義的 babel-jest 轉換器。我不敢100%確定這個是必須的,但我還是加上了。設置它用於在重新編譯所有代碼時載入我們的 Babel 配置。

// Babel-Jest 轉換器
const babelJest = require('babel-jest');
const path = require('path');
const cwd = process.cwd();
const babelConfig = require(path.resolve(cwd, 'babel.config'));
module.exports = babelJest.createTransformer(babelConfig);

這些都配置好後,你在測試代碼應該又能跑了。記住了,任何使用庫的 es2015 模塊都需要這樣配置,不然測試代碼跑不動。

接下來輪到另一個痛點了:鏈接庫。使用 npm/yarn 鏈接的過程就是創建一個指向本地項目目錄的符號鏈接。結果表明,Babel 在重新編譯通過這種方式鏈接的庫時,會拋出很多錯誤。我之所以花了這麼長時間才弄清楚 Jest 這檔子事兒,原因之一就是我一直通過這種方式鏈接我的庫,出現了一堆錯誤。

解決辦法就是:不要使用 npm/yarn link。用類似 “yalc” 這樣的工具,它可以連接本地項目,同時能模擬正常的 npm 安裝過程。它不但沒有 Babel 重編譯的問題,還能更好地處理傳遞性依賴。

針對特定庫的優化。

如果完成了以上所有步驟,你的應用基本上實現了比較健壯的 tree shaking。不過,為了進一步減少文件包大小,你還可以做一些事情。我會列舉一些特定庫的優化方法,但這絕對不是全部。它尤其能為我們提供靈感,做出一些更酷的事情。

MomentJS 是出了名的大體積庫。幸好它可以剔除多語言包來減少體積。在下麵的代碼示例中,我排除了 momentjs 所有的多語言包,只保留了基本部分,體積明顯小了很多。

// 用 IgnorePlugin 移除多語言包
const { IgnorePlugin } from 'webpack';
const config = {
 plugins: [
  new IgnorePlugin(/^\.\/locale$/, /moment/)
 ]
};

Moment-Timezone 是 MomentJS 的老表,也是個大塊頭。它的體積基本上是一個帶有時區信息的超大 JSON 文件導致的。我發現只要保留本世紀的年份數據,就可以將體積縮小90%。這種情況需要用到一個特殊的 Webpack 插件。

// MomentTimezone Webpack Plugin
const MomentTimezoneDataPlugin = require('moment-timezone-data-webpack-plugin');
const config = {
 plugins: [
  new MomentTimezoneDataPlugin({
   startYear: 2018,
   endYear: 2100
  })
 ]
};

Lodash 是另一個導致文件包膨脹的大塊頭。幸好有一個替代包 Lodash-es,它被編譯成 es2015 模塊,並帶有 sideEffects 標誌。用它替換 Lodash 可以進一步縮減包的大小。

另外,Lodash-es,react-bootstrap 以及其他庫可以在 Babel transform imports 插件的幫助下實現瘦身。該插件從庫的 index.js 文件讀取 import 語句,並使其指向庫中特定文件。這樣就使 webpack 在解析模塊樹時更容易對庫做 tree shaking。下麵的例子演示了它是如何工作的。

// Babel Transform Imports
// Babel config
const config = {
 plugins: [
  [
   'transform-imports',
   {
    'lodash-es': {
     transform: 'lodash/${member}',
     preventFullImport: true
    },
    'react-bootstrap': {
     transform: 'react-bootstrap/es/${member}', // The es folder contains es2015 module versions of the files
     preventFullImport: true
    }
   }
  ]
 ]
};
// 這些庫不再支持全量導入,否則會報錯
import _ from 'lodash-es';
// 具名導入依然支持
import { debounce } from 'loash-es';
// 不過這些具名導入會被babel編譯成這樣子
// import debounce from 'lodash-es/debounce';

總結

全文到此結束。這樣的優化可以極大地縮減打包後的大小。隨著前端架構開始有了新的方向(比如微前端),保持包大小最優化變得比以往更加重要。希望本文能給那些正在給應用程式做tree shaking的同學帶來一些幫助。

交流

歡迎掃碼關註微信公眾號“1024譯站”,為你奉上更多技術乾貨。
公眾號:1024譯站


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2019年11月08日 數磚的 Xingbo Jiang 大佬給社區發了一封郵件,宣佈 Apache Spark 3.0 預覽版正式發佈,這個版本主要是為了對即將發佈的 Apache Spark 3.0 版本進行大規模社區測試。無論是從 API 還是從功能上來說,這個預覽版都不是一個穩定的版本,它的 ...
  • 公眾號回覆 獲取安裝包 項目地址: "Wanandroid Compose" 經過前段時間的 Android Dev Summit ,相信你已經大概瞭解了 Jetpack Compose 。如果你還沒有聽說過,可以閱讀這篇文章 "Jetpack Compose 最新進展" 。總而言之,Compose ...
  • 概述PhotoKit應該是iOS 8 開始引入為了替代之前ALAssetsLibrary的相冊資源訪問的標準庫,後者在iOS 9開始被棄用。當然相對於ALAssetsLibrary其擴展性更高,api使用起來也更加的強大,但這並非今天討論的重點,這裡主要討論PhotoKit使用的一些技巧和容易踩的坑... ...
  • 做一個好看的登錄註冊界面 前言 最近在嘗試做網盤,使用的技術棧大概是 .net core + MVC + Mysql + Layui ,主要目的是通過這個具體的項目,熟悉熟悉 .net core 開發, .net 的未來就是他了! 我的設想 在完成後端的一部分 建設 之後,我把目光投向了前端——登陸 ...
  • 1.v-if和v-show v-if 和v-show都可以顯示和隱藏元素; 區別:(1)v-if初始值為false那麼這個元素不會被渲染 ,v-show不管初始值為何值都會被渲染 (2)v-if是控制DOM元素是否插入,v-show是控制css的display屬性 (3)v-if適合隱藏尚未載入的內 ...
  • 設置行高 由於簡單還是老樣子直接上代碼了哦,註意: 屬性值可以使用固定值如:20px..和百分比如:20%。 如果想讓文字垂直居中如下:行高的主要作用是用來設置文本的垂直方向居中對齊行高的值與高度的值一樣即可。 讓我們進入 屬性實踐,實踐內容如:將 標簽行高設置為20px。 代碼塊 結果圖 註意:使 ...
  • 設置字元和單詞間距介紹 屬性名 | 單位 |描述 | | letter spacing | px|設置字元間距 word spacing | px |設置單詞間距 letter spacing設置字元間距 屬性原理是:根據要設置的文本每一個字元之間的間距。 讓我們進入 屬性的實踐,實踐內容如:將 頁 ...
  • text indent屬性介紹 屬性值單位 | 描述 | em | 比如:1em 就代表縮進1個字,2em縮進2個字.....。 由於簡單我就不過多的介紹了直接上代碼了哦,註意: 屬性的值支持為負數具體園友可以嘗試下。 代碼塊 結果圖 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...