這是我參與8月更文挑戰的第1天,活動詳情查看:8月更文挑戰html
首先感謝若川大佬組織的此次源碼閱讀,原文地址聽說 99% 的人不知道 vue-devtools 還能直接打開對應組件文件?本文原理揭祕vue
對於vue開發者來講。對這個再熟悉不過了,若是不熟悉的參考一下文檔vue-devtools。node
此次主要是探尋第4點的原理linux
原理很簡單,就是使用code
命令,webpack
code xxx
複製代碼
在命令後工具中輸入code
,若是出現如下提示就是 本機沒有code,須要安裝git
~ % code
zsh: command not found: code
複製代碼
在mac中的vscode中 使用 command + shift + p
能夠召喚出安裝窗口,輸入shell 就能夠看到,而後點擊安裝github
打開準備好的vue3項目,找到package.json文件,能夠看到debugweb
在vue-devtools官網能夠看到,支持 Open Components in Editor 的功能來自 launch-editor-middleware。vuex
1.vue-cli 3支持這個功能。開箱即用vue-cli
Vue CLI 3 supports this feature out-of-the-box when running vue-cli-service serve.
複製代碼
2.也能夠經過webpack單獨引入,具體能夠參考官方文檔 webpack中使用open-in-editor
3.經過官方文檔得知,如何喚起這個功能,是經過express
app.use('/__open-in-editor', openInEditor())
複製代碼
當觸發該功能的時候,能夠在network中看到有個以下的請求。
4.咱們在 node_modules 中搜索 launch-editor-middleware
這個包,查看index.js這個文件
看到其中的代碼以下:
const url = require('url')
const path = require('path')
const launch = require('launch-editor')
module.exports = (specifiedEditor, srcRoot, onErrorCallback) => {
if (typeof specifiedEditor === 'function') {
onErrorCallback = specifiedEditor
specifiedEditor = undefined
}
if (typeof srcRoot === 'function') {
onErrorCallback = srcRoot
srcRoot = undefined
}
srcRoot = srcRoot || process.cwd()
return function launchEditorMiddleware (req, res, next) {
const { file } = url.parse(req.url, true).query || {}
if (!file) {
res.statusCode = 500
res.end(`launch-editor-middleware: required query param "file" is missing.`)
} else {
launch(path.resolve(srcRoot, file), specifiedEditor, onErrorCallback)
res.end()
}
}
}
複製代碼
從總體來看,整個方法是採用了一個閉包的方式實現一個模塊化。
4~13行,經過判斷傳入的類型,來給變量從新賦值,簡化了操做者的使用,在不想要傳入所有參數的時候傳入最後一個參數。
15行,做用是獲取當前文件的根目錄,若是沒有傳入就使用,執行serve命令的目錄
17行之後就是判斷是否有文件,若是沒有文件就返回,launch-editor-middleware: required query param "file" is missing.
不然就執行launch的方法,該方法來自 launch-editor
.
此時咱們能夠在 launch 函數這裏打一個斷點。
而後,在啓動的項目上使用vue-devtools 打開文,不出意外會在此處中止。
在右上角會出現一個調試工具條,第一個是繼續,第二個是跳過當前,第三個是進入到next call中,第四個是跳出。 此時咱們能夠三個參數分別是什麼:
關注一下 onErrorCallback 他的提示
`To specify an editor, specify the EDITOR env variable or `
`add "editor" field to your Vue project config. 複製代碼
後面咱們解釋一下是什麼意思,這裏可能有些人會遇到打開不到指定文件的問題。
先看主函數
function launchEditor (file, specifiedEditor, onErrorCallback) {
// 首先這裏會解析文件
const parsed = parseFile(file)
let { fileName } = parsed
const { lineNumber, columnNumber } = parsed
// 判斷文件是否存在
if (!fs.existsSync(fileName)) {
return
}
// 前面說過了
if (typeof specifiedEditor === 'function') {
onErrorCallback = specifiedEditor
specifiedEditor = undefined
}
onErrorCallback = wrapErrorCallback(onErrorCallback)
// 看名字,大概是猜想編輯器的意思
const [editor, ...args] = guessEditor(specifiedEditor)
if (!editor) {
onErrorCallback(fileName, null)
return
}
// 使用process 來判斷當前系統
// 一些兼容問題
if (
process.platform === 'linux' &&
fileName.startsWith('/mnt/') &&
/Microsoft/i.test(os.release())
) {
// Assume WSL / "Bash on Ubuntu on Windows" is being used, and
// that the file exists on the Windows file system.
// `os.release()` is "4.4.0-43-Microsoft" in the current release
// build of WSL, see: https://github.com/Microsoft/BashOnWindows/issues/423#issuecomment-221627364
// When a Windows editor is specified, interop functionality can
// handle the path translation, but only if a relative path is used.
fileName = path.relative('', fileName)
}
if (lineNumber) {
const extraArgs = getArgumentsForPosition(editor, fileName, lineNumber, columnNumber)
args.push.apply(args, extraArgs)
} else {
args.push(fileName)
}
// 防止二次綁定,若是已經有一個進程,而且是命令行工具的時候就kill 掉
if (_childProcess && isTerminalEditor(editor)) {
// There's an existing editor process already and it's attached
// to the terminal, so go kill it. Otherwise two separate editor
// instances attach to the stdin/stdout which gets confusing.
_childProcess.kill('SIGKILL')
}
// 若是是win32 就執行 cmd
if (process.platform === 'win32') {
// On Windows, launch the editor in a shell because spawn can only
// launch .exe files.
_childProcess = childProcess.spawn(
'cmd.exe',
['/C', editor].concat(args),
{ stdio: 'inherit' }
)
} else {
// 不然就xxx
_childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
}
// 退出
_childProcess.on('exit', function (errorCode) {
_childProcess = null
if (errorCode) {
onErrorCallback(fileName, '(code ' + errorCode + ')')
}
})
// 錯誤的處理
_childProcess.on('error', function (error) {
onErrorCallback(fileName, error.message)
})
}
module.exports = launchEditor
複製代碼
分析完上面的主函數,能夠看出,真正執行打開文件的 代碼 只有 這幾行:
if (process.platform === 'win32') {
// On Windows, launch the editor in a shell because spawn can only
// launch .exe files.
_childProcess = childProcess.spawn(
'cmd.exe',
['/C', editor].concat(args),
{ stdio: 'inherit' }
)
} else {
// 不然就xxx
_childProcess = childProcess.spawn(editor, args, { stdio: 'inherit' })
}
複製代碼
我屁顛屁顛的去瀏覽器上想打開對應的文件。惋惜很遺憾的是,我沒有打開,而是在控制檯把整個文件的內容輸出了。
因而我在
打了一個斷點,因而發現
臥槽,個人editor 怎麼是vi,怎麼不是code
?
因而我就看在哪裏獲取的 editor,原來是經過guessEditor
獲取的,因而在這個函數這裏打個斷點
const [editor, ...args] = guessEditor(specifiedEditor)
複製代碼
guessEditor的源碼以下:
const path = require('path')
const shellQuote = require('shell-quote')
const childProcess = require('child_process')
// Map from full process name to binary that starts the process
// We can't just re-use full process name, because it will spawn a new instance
// of the app every time
// 這些文件裏存了 各個系統編輯器可能安裝的目錄
const COMMON_EDITORS_OSX = require('./editor-info/osx')
const COMMON_EDITORS_LINUX = require('./editor-info/linux')
const COMMON_EDITORS_WIN = require('./editor-info/windows')
module.exports = function guessEditor (specifiedEditor) {
// 若是有指定的編輯器
if (specifiedEditor) {
return shellQuote.parse(specifiedEditor)
}
// We can find out which editor is currently running by:
// `ps x` on macOS and Linux
// `Get-Process` on Windows
// 經過使用 子進程的 執行 ps x 來獲取全部進程,而後拿到key
// 若是經過遍歷 獲取有沒有匹配的,若是有匹配的 就返回對應的編輯器執行打開文件的命令
/* '/Applications/Sublime Text 2.app/Contents/MacOS/Sublime Text 2': '/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl', '/Applications/Sublime Text Dev.app/Contents/MacOS/Sublime Text': '/Applications/Sublime Text Dev.app/Contents/SharedSupport/bin/subl', '/Applications/Visual Studio Code.app/Contents/MacOS/Electron': 'code', '/Applications/Visual Studio Code - Insiders.app/Contents/MacOS/Electron': */
try {
if (process.platform === 'darwin') {
const output = childProcess.execSync('ps x').toString()
const processNames = Object.keys(COMMON_EDITORS_OSX)
for (let i = 0; i < processNames.length; i++) {
const processName = processNames[i]
if (output.indexOf(processName) !== -1) {
return [COMMON_EDITORS_OSX[processName]]
}
}
} else if (process.platform === 'win32') {
const output = childProcess
.execSync('powershell -Command "Get-Process | Select-Object Path"', {
stdio: ['pipe', 'pipe', 'ignore']
})
.toString()
const runningProcesses = output.split('\r\n')
for (let i = 0; i < runningProcesses.length; i++) {
// `Get-Process` sometimes returns empty lines
if (!runningProcesses[i]) {
continue
}
const fullProcessPath = runningProcesses[i].trim()
const shortProcessName = path.basename(fullProcessPath)
if (COMMON_EDITORS_WIN.indexOf(shortProcessName) !== -1) {
return [fullProcessPath]
}
}
} else if (process.platform === 'linux') {
// --no-heading No header line
// x List all processes owned by you
// -o comm Need only names column
const output = childProcess
.execSync('ps x --no-heading -o comm --sort=comm')
.toString()
const processNames = Object.keys(COMMON_EDITORS_LINUX)
for (let i = 0; i < processNames.length; i++) {
const processName = processNames[i]
if (output.indexOf(processName) !== -1) {
return [COMMON_EDITORS_LINUX[processName]]
}
}
}
} catch (error) {
// Ignore...
}
// Last resort, use old skool env vars
// 此處獲取自定義的環境變量
if (process.env.VISUAL) {
return [process.env.VISUAL]
} else if (process.env.EDITOR) {
return [process.env.EDITOR]
}
return [null]
}
複製代碼
經過調試得知,沒有guess到正確的,命令。 因此執行到了獲取自定義的環境變量這裏。在 vue cli 環境變量這裏說到 如何添加環境變量,經過添加.env[mode]
來添加環境變量。
我添加了一個editor = code
的環境變量,這時候我又從新試了一下。發現怎麼仍是不行?仍是vi,因而我又試了一下添加VISUAL = code
,此次很好。真的能夠了,成功啦!!
因而我猜測,vue-cli中設置使用.env
,那麼應該是在vue-cli中設置的環境變量。因而繼續使用 調試打開。找到 vue-cli-serve。在@vue/cli-service的bin下面,看到了vue-cli-service.js
經過上面代碼能夠看到,是使用了lib/Service 在lib/Service
中,我找到了 loadEnv
loadEnv (mode) {
const logger = debug('vue:env')
const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
const localPath = `${basePath}.local`
const load = envPath => {
try {
const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
dotenvExpand(env)
logger(envPath, env)
} catch (err) {
// only ignore error if file is not found
if (err.toString().indexOf('ENOENT') < 0) {
error(err)
}
}
}
load(localPath)
load(basePath)
// by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
// is production or test. However the value in .env files will take higher
// priority.
if (mode) {
// always set NODE_ENV during tests
// as that is necessary for tests to not be affected by each other
const shouldForceDefaultEnv = (
process.env.VUE_CLI_TEST &&
!process.env.VUE_CLI_TEST_TESTING_ENV
)
const defaultNodeEnv = (mode === 'production' || mode === 'test')
? mode
: 'development'
if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {
process.env.NODE_ENV = defaultNodeEnv
}
if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {
process.env.BABEL_ENV = defaultNodeEnv
}
}
}
複製代碼
loadEnv 主要load兩種 env,一種是local
一種是base
local是.env.local中配置的
base是.env.development相似的。具體能夠看官方文檔他們之間的區別。
而後經過 dotenv
這個庫去設置process.env
,讀取.env文件也是dotenv提供的功能。
因而繼續打斷點進入 dotenv
, 在其中看到了config函數
// Populates process.env from .env file
function config (options /*: ?DotenvConfigOptions */) /*: DotenvConfigOutput */ {
let dotenvPath = path.resolve(process.cwd(), '.env')
let encoding /*: string */ = 'utf8'
let debug = false
if (options) {
if (options.path != null) {
dotenvPath = options.path
}
if (options.encoding != null) {
encoding = options.encoding
}
if (options.debug != null) {
debug = true
}
}
try {
// specifying an encoding returns a string instead of a buffer
const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug })
Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key]
} else if (debug) {
log(`"${key}" is already defined in \`process.env\` and will not be overwritten`)
}
})
return { parsed }
} catch (e) {
return { error: e }
}
}
複製代碼
在,第27行看到了
若是參數在process.env中已經存在,將不會被覆蓋。 破案了,在控制檯輸入 process.env 能夠看到,EDITOR有個默認值,爲vi
個人node版本是v16.4.2,通過查閱官方文檔 process_process_env,得知:
EDITOR/VISUAL
The user's preferred utility to edit text files. Any
string acceptable as a command_string operand to the sh -c
command shall be valid.
複製代碼
到此終於明白了。
經過vue-cli-sever的實現,是否其實能夠將團隊的配置統一化,集成到團隊腳手架中去,以此來爲規範化提供便利,達到開箱即用的效果。
關注我共同進步