針對一些共用工具程式庫,我習慣在專案加入docfx.msbuild,每次編譯就同步產出API文件,讓文件永遠與最新版程式同步,十分方便。
不過開發久了便覺得每次編譯都重新產生文件會拖累效率,不是個好主意。以手邊的一個程式庫專案為例,沒加上DocFx前大約一秒內就能編譯完成,DocFx文件製作較耗時,動輒要耗用5-6秒,編譯時間整整拖長五倍以上,對性急如火人生苦短的中年程序員來說,彷彿感受到寶貴的職業生涯正在平白流逝,眼看累積3000安的希望愈來愈渺茫,很是煎熬。
以下是一個實例,DocFx編譯部分就花了5.821秒:
2> Verbose: [Build Document.Apply Templates]Resource "partials/namespaceSubtitle.tmpl.partial" is found from "embedded resource docfx.Template.default.zip"
2> Verbose: [Build Document.Apply Templates]Transformed model "X:\TFS\src\MyWebApi.Client\obj\api\MyWebApi.Models.yml" to "_site\api/MyWebApi.Models.html".
2> Info: [Build Document.Apply Templates]Manifest file saved to X:\TFS\src\MyWebApi.Client\_site\manifest.json.
2> Info: [Build Document]Building 85 file(s) completed.
2> Info: Completed building documents in 3927.7096 milliseconds.
2> Verbose: Disposing processor ConceptualDocumentProcessor ...
2> Verbose: Disposing build step BuildConceptualDocument ...
2> Verbose: Disposing build step CountWord ...
2> Verbose: Disposing processor ManagedReferenceDocumentProcessor ...
2> Verbose: Disposing build step ApplyOverwriteDocumentForMref ...
2> Verbose: Disposing build step BuildManagedReferenceDocument ...
2> Verbose: Disposing build step FillReferenceInformation ...
2> Verbose: Disposing processor ResourceDocumentProcessor ...
2> Verbose: Disposing processor RestApiDocumentProcessor ...
2> Verbose: Disposing build step ApplyOverwriteDocumentForRestApi ...
2> Verbose: Disposing build step BuildRestApiDocument ...
2> Verbose: Disposing processor TocDocumentProcessor ...
2> Verbose: Disposing build step BuildTocDocument ...
2> Info: [Apply Theme]Theme is applied.
2> Info: Completed executing in 5821.8844 milliseconds.
2>
2>
2> Build succeeded.
2> 0 Warning(s)
2> 0 Error(s)
3>------ Build started: Project: ITForms, Configuration: Debug Any CPU ------
3> ITForms -> X:\TFS\src\FORM\ITForms\bin\ITForms.dll
========== Build: 3 succeeded, 0 failed, 3 up-to-date, 0 skipped ==========
然而,真有必要每次編譯都重製API文件嗎?事實不然,理論上API文件只有在型別、方法介面變動時才需要更新。開發階段有很高比例的編譯動作是為了修Bug、調整寫法,此時更新API文件純屬空耗資源,平白浪費時間。
為拯救中年程序員所剩無幾的青春,我開始研究如何在專案加個開關,必要時再產生文件,平時則略過DocFx程序以縮短編譯時間。憑藉先前研究TFS Build Serivce時對MSBuild與.csproj結構的粗淺了解,我在csproj裡找到以下這段:
<ImportProject="..\packages\docfx.msbuild.1.4.2\build\docfx.msbuild.targets"
Condition="Exists('..\packages\docfx.msbuild.1.4.2\build\docfx.msbuild.targets')"/>
想必這就是DocFx編譯程序,上面已設定Condition條件,docfx.msbuild.targets檔案存在才執行。修改Condition條件,我加上$(DefineConstants.Contains('DOCFX')),指定當定義DOCFX常數時才啟用DocFx編譯。另外,將DocFx文件複製至指定位置的動作則寫成Builds後執行的Target,一定限定遇到DOCFX常數才執行。
<ImportProject="..\packages\docfx.msbuild.1.4.2\build\docfx.msbuild.targets"
Condition="$(DefineConstants.Contains('DOCFX')) AND
Exists('..\packages\docfx.msbuild.1.4.2\build\docfx.msbuild.targets')"/>
<TargetName="Copy DocFx Files"Condition="'$(BuildingInsideVisualStudio)' == ''"
AfterTargets="Build">
<Exec
Command="xcopy /Y /S "$(ProjectDir)_site" "$(TargetDir)..\..\..\WebApi\Docs""
Condition="$(DefineConstants.Contains('DOCFX'))">
</Exec>
</Target>
至於DOCFX常數要如何設定?如下圖Condiional compilation symbols處,平時不加DOCFX以求快速編譯,當API介面有異動時再加上DOCFX產生文件。
歷經這番調整,算是兼顧效能與功能,編譯專案也恢復往日的速度,不再陷入「花1秒改程式,等10秒看結果」的焦慮,好多了!