1. 程式人生 > >gitbook 入門教程之解決windows熱載入失敗問題

gitbook 入門教程之解決windows熱載入失敗問題

破鏡如何貼花黃

gitbookWindows 系統無法熱載入,總是報錯!

gitbook 是一款文件編寫利器,可以方便地 markdown 輸出成美觀優雅的 html ,gitbook serve 啟動伺服器後,原來相貌平平的 markdown 醜小鴨搖身一變就成了傾國傾城的 html 絕色佳人.

如果原始檔發生更改,Windows 卻無法按照預期那樣重啟伺服器,直接丟擲一個異常,立即終止了 markdown 的化妝.

Restart after change in file README.md

Stopping server
events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: EPERM: operation not permitted, lstat 'F:\workspace\private-cloud-backup\gitbook-test\_book'

對鏡貼花黃

現在看一下 markdown 灰姑娘變身 html 小姐姐的神奇過程吧!

$ gitbook serve --log=debug
Live reload server started on port: 35729
Press CTRL+C to quit ...

debug: readme found at README.md
debug: summary file found at SUMMARY.md
debug: cleanup folder "G:\sublime\gitbook-test\_book"
info: 7 plugins are installed
info: loading plugin "livereload"... OK
...
info: loading plugin "theme-default"... OK
info: found 1 pages
info: found 0 asset files
debug: calling hook "config"
debug: calling hook "init"
debug: copy assets from theme C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-theme-default\_assets\website
...
debug: copy resources from plugin C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-livereload\book
debug: generate page "README.md"
debug: calling hook "page:before"
debug: calling hook "page"
debug: index page README.md
debug: calling hook "finish:before"
debug: calling hook "finish"
debug: write search index
info: >> generation finished with success in 1.5s !

Starting server ...
Serving book on http://localhost:4000

根據上述輸出日誌,我們可以分析出 gitbook 的基本執行流程.

  • 載入 readmesummary 檔案,若存在 glossary 檔案也會載入,並刪除 _book 目錄
debug: readme found at README.md
debug: summary file found at SUMMARY.md
debug: cleanup folder "G:\sublime\gitbook-test\_book"
  • 載入依賴外掛,若沒有找到相應外掛會報錯,提示執行 gitbook install 安裝外掛.
info: 7 plugins are installed
info: loading plugin "livereload"... OK
info: loading plugin "highlight"... OK
info: loading plugin "search"... OK
info: loading plugin "lunr"... OK
info: loading plugin "sharing"... OK
info: loading plugin "fontsettings"... OK
info: loading plugin "theme-default"... OK
  • 掃描頁面和靜態資原始檔
info: found 1 pages
info: found 0 asset files
  • 讀取配置檔案並初始化
debug: calling hook "config"
debug: calling hook "init"
  • 拷貝樣式資源和外掛資源
debug: copy assets from theme C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-theme-default\_assets\website
debug: copy resources from plugin C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-fontsettings\assets
debug: copy resources from plugin C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-sharing\assets
debug: copy resources from plugin C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-lunr\assets
debug: copy resources from plugin C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-search\assets
debug: copy resources from plugin C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-highlight\css
debug: copy resources from plugin C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\gitbook-plugin-livereload\book
  • 開始生成單獨頁面,依次執行 page:before ,page 回撥函式,全部頁面執行完畢後執行 finish:beforefinish 回撥函式.
debug: generate page "README.md"
debug: calling hook "page:before"
debug: calling hook "page"
debug: index page README.md
debug: calling hook "finish:before"
debug: calling hook "finish"
  • 生成搜尋檔案
debug: write search index
  • 啟動完畢,輸出成功資訊
Starting server ...
Serving book on http://localhost:4000

預設情況下伺服器啟動後會佔用兩個埠,一個是對外暴露的 4000 埠,用於瀏覽器訪問專案.

另外一個是 35729 埠,用於監聽本地檔案變化,重啟伺服器進而實現熱載入功能.

本地伺服器啟動後我們就可以訪問 http://localhost:4000 預覽靜態網站效果,markdown 原始檔華麗演變成 html 富文字檔案.

破鏡怎化妝

不幸的是,Windows 熱載入可能會有問題,也就是說如果啟動伺服器後,本地檔案發生改變,此時會觸發熱載入功能而報錯 Error: EPERM: operation not permitted ,這樣一來瀏覽器又無法訪問了.

剛剛變身的 markdown 瞬間又被打回原形,無法欣賞化妝後的容顏了,這樣的體驗相當不好!

邊化妝邊照鏡子才是做到心中有譜,隨時調整,如果不照鏡子而直接化妝,那不是一般人能做到的.

gitbook 啟動本地伺服器給我們提供了鏡子,但熱載入失敗又把鏡子摔碎了,還怎麼愉快的化妝?

Restart after change in file README.md

Stopping server
debug: readme found at README.md
debug: summary file found at SUMMARY.md
debug: cleanup folder "G:\sublime\gitbook-test\_book"
events.js:174
      throw er; // Unhandled 'error' event
      ^

Error: EPERM: operation not permitted, lstat 'G:\sublime\gitbook-test\_book'
Emitted 'error' event at:
    at FSWatcher._handleError (C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\chokidar\index.js:236:10)
    at ReaddirpReadable.emit (events.js:189:13)
    at Immediate.<anonymous> (C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\chokidar\node_modules\readdirp\stream-api.js:82:32)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5)

尋醫問診修破鏡

現在問題已經復現,接下來就要開始尋醫問診,試圖讓破鏡重圓,好讓 markdown 灰姑娘變成人見人愛的 html 小姐姐.

根據報錯資訊描述,定位到刪除 _book 目錄再次建立該目錄時,提示 EPERM: operation not permitted ,即無權操作.

柯南附體

既然說是操作許可權的問題,那我們看一下 _book 目錄現在是怎樣狀態吧!

$ ls
gitbook-errorforwindows-preview.png  README.md  SUMMARY.md

當前專案已經沒有 _book 目錄,證明發生報錯時確實已經刪除了 _book 目錄,但是某種原因無權再次建立該資料夾而重啟失敗.

然而,這只是表現現象,老師告訴我們,要透過現象看本質,即使現在沒有 _book 檔案再次啟動伺服器還是會啟動成功並建立 _book 檔案的,所以真想只有一個!

那就是,gitbook 控制檯在說謊!

雖然排除了 gitbook 無權建立 _book 目錄的嫌疑,那又怎麼解釋重啟伺服器卻沒能建立 _book目錄這件事呢?

debug: cleanup folder "G:\sublime\gitbook-test\_book"
events.js:174
      throw er; // Unhandled 'error' event
      ^

Error: EPERM: operation not permitted, lstat 'G:\sublime\gitbook-test\_book'
Emitted 'error' event at:
    at FSWatcher._handleError (C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\chokidar\index.js:236:10)
    at ReaddirpReadable.emit (events.js:189:13)
    at Immediate.<anonymous> (C:\Users\snowdreams1006\.gitbook\versions\3.2.3\node_modules\chokidar\node_modules\readdirp\stream-api.js:82:32)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5)

先看一下 FSWatcher._handleError 異常資訊: sed -n "223,239p" ~/.gitbook/versions/3.2.3/node_modules/chokidar/index.js .

分析發現: FSWatcher._handleError 是私有方法,作用是處理異常資訊,和這起事故關聯不大.

Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master)
$ sed -n "223,239p" ~/.gitbook/versions/3.2.3/node_modules/chokidar/index.js
// Private method: Common handler for errors
//
// * error  - object, Error instance
//
// Returns the error if defined, otherwise the value of the
// FSWatcher instance's `closed` flag
FSWatcher.prototype._handleError = function(error) {
  var code = error && error.code;
  var ipe = this.options.ignorePermissionErrors;
  if (error &&
    code !== 'ENOENT' &&
    code !== 'ENOTDIR' &&
    (!ipe || (code !== 'EPERM' && code !== 'EACCES'))
  ) this.emit('error', error);
  return error || this.closed;
};

我們接著往下找,再看一下 ReaddirpReadable.emit (events.js:189:13) ,這裡沒有給出檔案的具體路徑,所以暫時無法定位.

那我們再看下一個 Immediate.<anonymous> : sed -n "78,96p" ~/.gitbook/versions/3.2.3/node_modules/chokidar/node_modules/readdirp/stream-api.js

Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master)
$ sed -n "78,96p" ~/.gitbook/versions/3.2.3/node_modules/chokidar/node_modules/readdirp/stream-api.js
proto._handleFatalError = function (err) {
  var self = this;
  setImmediate(function () {
    if (self._paused) return self._errors.push(err);
    if (!self._destroyed) self.emit('error', err);
  });
}

function createStreamAPI () {
  var stream = new ReaddirpReadable();

  return {
      stream           :  stream
    , processEntry     :  stream._processEntry.bind(stream)
    , done             :  stream._done.bind(stream)
    , handleError      :  stream._handleError.bind(stream)
    , handleFatalError :  stream._handleFatalError.bind(stream)
  };
}

遺憾的是,仍然沒有找到具體問題,那就繼續看一下一條線索.

timers.js:705:18events.js:189:13 都沒有顯示具體的檔案位置,如果也在 chokidar 模組的話就好了.

Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master)
$ tree -P "events.js" --prune ~/.gitbook/versions/3.2.3/
/c/Users/Administrator/.gitbook/versions/3.2.3/
└── node_modules
    ├── cheerio
    │   └── node_modules
    │       └── jsdom
    │           └── lib
    │               └── jsdom
    │                   └── level2
    │                       └── events.js
    └── gitbook-plugin-theme-default
        └── src
            └── js
                └── core
                    └── events.js

11 directories, 2 files

Administrator@snowdreams1006 MINGW64 /f/workspace/private-cloud-backup/gitbook-test (master)
$ tree -P "timers.js" --prune ~/.gitbook/versions/3.2.3/
/c/Users/Administrator/.gitbook/versions/3.2.3/
0 directories, 0 files

git-bash 命令列正常沒有 tree 命令,如需擴充套件參考我另外一篇文章.

經過肉眼驗證,發現 events.js 根本就沒有 174 行檔案,所以這兩個檔案大都不是目標檔案.

既然命令列中無法找到目標檔案,那就請專業的搜尋工具全系統查詢這兩個檔案吧,這裡使用的是 Everything 搜尋工具.

然並卵,依然沒有找到目標檔案.

畢竟不是柯南,沒有發現真相

求助官方

gitbook 可是開源產品,出現問題的應該不止我一個,所以去 github 看看有沒有遇到和我一樣的問題.

雖然找到了志同道合的小夥伴,但是並沒有提供解決方案,連官方都放棄了,那我還有什麼可留戀的?

點選檢視 gitbook serve livereload error

自己動手

最害怕的不是 bug,而是發現了 bug 卻無法定位,雖然控制檯有報錯資訊但是沒有找到真正的檔案!

首先確認下當前系統版本,然後採取版本切換方式測試其他版本是否存在該問題.

$ gitbook --version
CLI version: 2.3.2
GitBook version: 3.2.3
  • 升級到最新版

gitbook ls 是列出當前已安裝的版本,而 gitbook ls-remote 則是列出遠端伺服器版本.

# 列出本地已安裝版本
$ gitbook ls
GitBook Versions Installed:

    * 3.2.3

Run "gitbook update" to update to the latest version.

# 列出遠端可用版本
$ gitbook ls-remote
Available GitBook Versions:

     4.0.0-alpha.6, 4.0.0-alpha.5, 4.0.0-alpha.4, 4.0.0-alpha.3, 4.0.0-alpha.2, 4.0.0-alpha.1, 3.2.3, 3.2.2, 3.2.1, 3.2.0, 3.2.0-pre.1, 3.2.0-pre.0, 3.1.1, 3.1.0, 3.0.3, 3.0.2, 3.0.1, 3.0.0, 3.0.0-pre.15, 3.0.0-pre.14, 3.0.0-pre.13, 3.0.0-pre.12, 3.0.0-pre.11, 3.0.0-pre.10, 3.0.0-pre.9, 3.0.0-pre.8, 3.0.0-pre.7, 3.0.0-pre.6, 3.0.0-pre.5, 3.0.0-pre.4, 3.0.0-pre.3, 3.0.0-pre.2, 3.0.0-pre.1, 2.6.9, 2.6.8, 2.6.7, 2.6.6, 2.6.5, 2.6.4, 2.6.3, 2.6.2, 2.6.1, 2.6.0, 2.5.2, 2.5.1, 2.5.0, 2.5.0-beta.7, 2.5.0-beta.6, 2.5.0-beta.5, 2.5.0-beta.4, 2.5.0-beta.3, 2.5.0-beta.2, 2.5.0-beta.1, 2.4.3, 2.4.2, 2.4.1, 2.4.0, 2.3.3, 2.3.2, 2.3.1, 2.3.0, 2.2.0, 2.1.0, 2.0.4, 2.0.3, 2.0.2, 2.0.1, 2.0.0, 2.0.0-beta.5, 2.0.0-beta.4, 2.0.0-beta.3, 2.0.0-beta.2, 2.0.0-beta.1, 2.0.0-alpha.9, 2.0.0-alpha.8, 2.0.0-alpha.7, 2.0.0-alpha.6, 2.0.0-alpha.5, 2.0.0-alpha.4, 2.0.0-alpha.3, 2.0.0-alpha.2, 2.0.0-alpha.1

Tags:

     latest : 2.6.9
     pre : 4.0.0-alpha.6

目前最新發布版本是 3.2.3 ,而我們本地已安裝的版本正是該版本,所以現在應該測試 4.0.0-alpha.6 版.

看到 4.0.0-alpha.6 心裡有些忐忑,根據版本管理約定,版本號一般有三部分組成,第一部分代表不相容的重大升級,第二部分代表主幹相容的功能升級,第三部分是小版本修復.

3.2.3 直接跨度到 4.0.0-alpha.6 意味著 gitbook 發生了重大重構!

算了,先下載試試看!

gitbook fetch 下載 和 gitbook update升級,兩種方式都可以體驗最新版本,這裡選擇下載方式方便進行不同版本的切換.

# 下載 `4.0.0-alpha.6` 版本
$ gitbook fetch 4.0.0-alpha.6
Installing GitBook 4.0.0-alpha.6
[email protected] C:\Users\SNOWDR~1\AppData\Local\Temp\tmp-8912hSrxNvTCrFEH\node_modules\gitbook
├── [email protected]
├── [email protected]
├── [email protected]
├── [email protected]
└── [email protected] ([email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected])

GitBook 4.0.0-alpha.6 has been installed

先看一下本地安裝 gitbook 版本,確保待會執行時使用最新的 4.0.0-alpha.6 版本.

# 列出本地已安裝版本
$ gitbook ls
GitBook Versions Installed:

    * 4.0.0-alpha.6
      3.2.3

Run "gitbook update" to update to the latest version.

# 列出當前正在使用版本
$ gitbook current
GitBook version is 3.2.3

gitbook serve --gitbook=4.0.0-alpha.6 --log=debug 執行 4.0.0-alpha.6 版本並列印 debug 級別日誌.

意外的是,竟然沒有連啟動都沒啟動成功,提示無法開啟 ~\.gitbook\versions\4.0.0-alpha.6\node_modules\gitbook-plugin-livereload\_assets\plugin.js 檔案.

回想到版本號規範,可能 v3v4 更改比較大,版本不相容吧,重新初始化專案試試看!

# 初始化專案並指定 `gitbook` 執行版本
$ gitbook init --gitbook=4.0.0-alpha.6
Warning: Accessing PropTypes via the main React package is deprecated, and will be removed in  React v16.0. Use the latest available v15.* prop-types package from npm instead. For info on usage, compatibility, migration and more, see https://fb.me/prop-types-docs
info: create SUMMARY.md
info: initialization is finished

然而,仍然還是同樣的報錯,依舊無法啟動.

$ gitbook serve --gitbook=4.0.0-alpha.6 --log=debug                             Warning: Accessing PropTypes via the main React package is deprecated, and will be removed in  React v16.0. Use the latest available v15.* prop-types package from npm instead. For info on usage, compatibility, migration and more, see https://fb.me/prop-types-docs
Live reload server started on port: 35729
Press CTRL+C to quit ...

...

Error: ENOENT: no such file or directory, open 'C:\Users\snowdreams1006\.gitbook\versions\4.0.0-alpha.6\node_modules\gitbook-plugin-livereload\_assets\plugin.js'

此路不通,再換一條,既然向上無法處理,那向下回退會不會有結果呢?

  • 回退版本

當前系統版本是 3.2.3,最新測試版本是 4.0.0-alpha.6 ,然而最近一次提交的版本卻是 2.6.9 ?

為什麼 gitbook-ci 管理的 gitbook 版本號會突然跳水,會不會有什麼貓膩,難不成修復了什麼 bug ?

$ gitbook ls-remote
Available GitBook Versions:

     4.0.0-alpha.6, 4.0.0-alpha.5, 4.0.0-alpha.4, 4.0.0-alpha.3, 4.0.0-alpha.2, 4.0.0-alpha.1, 3.2.3, 3.2.2, 3.2.1, 3.2.0, 3.2.0-pre.1, 3.2.0-pre.0, 3.1.1, 3.1.0, 3.0.3, 3.0.2, 3.0.1, 3.0.0, 3.0.0-pre.15, 3.0.0-pre.14, 3.0.0-pre.13, 3.0.0-pre.12, 3.0.0-pre.11, 3.0.0-pre.10, 3.0.0-pre.9, 3.0.0-pre.8, 3.0.0-pre.7, 3.0.0-pre.6, 3.0.0-pre.5, 3.0.0-pre.4, 3.0.0-pre.3, 3.0.0-pre.2, 3.0.0-pre.1, 2.6.9, 2.6.8, 2.6.7, 2.6.6, 2.6.5, 2.6.4, 2.6.3, 2.6.2, 2.6.1, 2.6.0, 2.5.2, 2.5.1, 2.5.0, 2.5.0-beta.7, 2.5.0-beta.6, 2.5.0-beta.5, 2.5.0-beta.4, 2.5.0-beta.3, 2.5.0-beta.2, 2.5.0-beta.1, 2.4.3, 2.4.2, 2.4.1, 2.4.0, 2.3.3, 2.3.2, 2.3.1, 2.3.0, 2.2.0, 2.1.0, 2.0.4, 2.0.3, 2.0.2, 2.0.1, 2.0.0, 2.0.0-beta.5, 2.0.0-beta.4, 2.0.0-beta.3, 2.0.0-beta.2, 2.0.0-beta.1, 2.0.0-alpha.9, 2.0.0-alpha.8, 2.0.0-alpha.7, 2.0.0-alpha.6, 2.0.0-alpha.5, 2.0.0-alpha.4, 2.0.0-alpha.3, 2.0.0-alpha.2, 2.0.0-alpha.1

Tags:

     latest : 2.6.9
     pre : 4.0.0-alpha.6

帶著這些疑問,不妨下載 2.6.9 版本試試,看一下能否熱載入?

gitbook serve --log=debug --gitbook=2.6.9 指定 gitbook 版本,依舊失敗!

$ gitbook serve --log=debug --gitbook=2.6.9
Error loading version latest: Error: Cannot find module 'q'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:582:15)
    at Function.Module._load (internal/modules/cjs/loader.js:508:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at Object.<anonymous> (C:\Users\myHome\.gitbook\versions\2.6.9\lib\index.js:3:9)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)

TypeError: Cannot read property 'commands' of null

重回現場

現在把目光再次聚焦到最初的案發現場,這一次只能背水一戰了,自己動手要麼豐衣足食要麼餓死凍死!

Stopping server
debug: readme found at README.md
debug: summary file found at SUMMARY.md
debug: cleanup folder "G:\sublime\private-cloud-backup\gitbook-test\_book"
events.js:174
      throw er; // Unhandled 'error' event
      ^

Error: EPERM: operation not permitted, lstat 'G:\sublime\private-cloud-backup\gitbook-test\_book'
Emitted 'error' event at:
    at FSWatcher._handleError (C:\Users\myHome\.gitbook\versions\3.2.3\node_modules\chokidar\index.js:236:10)
    at ReaddirpReadable.emit (events.js:189:13)
    at Immediate.<anonymous> (C:\Users\myHome\.gitbook\versions\3.2.3\node_modules\chokidar\node_modules\readdirp\stream-api.js:82:32)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)
    at processImmediate (timers.js:658:5)

關於上述錯誤描述中,在真相只有一個章節中已經探討過,當時得出的結論是 gitbook 是刪除 _book 資料夾再新建 _book 資料夾時發生了意外.

如果這個行為不是由 gitbook 發生而是由我們手動干預的話,也就是說,當成功啟動本地伺服器後並在即將發生熱載入之前,此時人為刪除 _book 資料夾,會發生什麼?

我的猜想是:

因為 gitbook 的熱載入機制是監聽本地檔案目錄系統發生改變,進而停止伺服器再重新啟動伺服器.

當我們手動刪除了 _book 資料夾,對於 gitbook 來說,再觸發重啟伺服器的那一刻來說,突然發現沒有 _book 資料夾,此時就不會刪除也不會新建時發生異常,相當於直接新建 _book 資料夾,變相把熱載入弄成了初始啟動模式!

希望蒼天不負我,如若不行,只能看原始碼邏輯找 bug 了!

你猜猜會怎麼樣? it works !

  • 在實驗中,gitbook serve --log=debug 啟動本地伺服器後,如果本地檔案發生修改會重啟失敗!

  • 但是,如果在啟動本地伺服器後立即刪除 _book 目錄,當本地檔案發生修改時重啟服務就能成功了.

到此為止,總算找到一個解決方案,那就是啟動服務後立即刪除 _book 目錄.

不算完美的總結

windows 系統上啟動 gitbook 服務後,如果本地檔案發生更改,熱加會失敗.

如果啟動伺服器後立即刪除 _book 目錄,那麼之後再怎麼修改本地檔案都能順利重啟.

目前還沒有找到問題的根源,下一次將深入原始碼繼續探討到底是哪裡出問題導致 Windows 系統無法重啟.

雖然及時刪除 _book 目錄並不算是很好的解決方案,但至少 markdown 灰姑娘又能化妝成 html 小姐姐了