查看原文html
這個問題要從一個想法提及。前端
D2Admin 是一個開源的,前端中後臺集成方案,原先是基於 vue-cli2,大概是向 vue-cli3 過渡時, 做者老李,想在頁面右下角加個 Toggle 點擊,跳到當前頁面源碼對應的 github 頁面。vue
確實很實用的功能,D2Admin 的 Demo 頁面太多了,想看某個頁面的源碼,對於不熟悉項目目錄結構的新手很不友好。node
這些頁面統一爲 .vue
組件,那麼轉換一下:如何獲取 vue 單文件自身源碼路徑?webpack
目前經歷了三個方案,最終目標是把自身路徑賦值到 this.$options.__source
上。目前方案 3 是最新的。git
直接使用 node 中的 __filename
變量:github
<template>
<h1>{{ $options.__source }}</h1>
</template>
<script>
export default {
mounted() {
this.$options.__source = __filename
}
}
</script>
複製代碼
由於 webpack 編譯時,會把源碼文件在內部轉爲 node 模塊,.vue
文件中的 script 內容也被轉換了, 其中的 __filename
在編譯時被運行,直接獲得當前文件自身路徑。web
使用這個變量還須要在 webpack 配置中啓用 node.__filename
:vue-cli
/* vue.config.js */
module.exports = {
// ...
chainWebpack: config => {
// ...
config.node
.set('__dirname', true) // 同理
.set('__filename', true)
}
};
複製代碼
__filename
獲得的路徑在部分 .vue
文件下並不許確,路徑可能還會帶附帶 querystring一開始,堅強的老李用這個方式,給上百個組件手動掛上了路徑,但總比手動寫死每一個路徑要好安全
在 loader 層面,其實 vue-loader
提供了一個 exposeFilename
選項,只要啓用, 就會給每一個 .vue
組件帶上 this.$options.__file
,上面有準確的路徑。
這樣只須要改 loader 配置:
/* vue.config.js */
module.exports = {
// ...
chainWebpack: config => {
// ...
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options.exposeFilename = true
return options
})
}
};
複製代碼
開發環境默認是開啓 exposeFilename
的。
此時組件內應該直接取 this.$options.__file
,內容大體爲 src/foo/bar.vue
。
vue-loader
在生產環境將 __file
賦值爲文件名,非路徑名,詳見文檔後來得知這個方法,老李就第一時間改代碼,刪了方案 1 中的全部附加代碼,結果發現生產環境結果不一致,翻車了orz
既然方案 2 不讓在生產環境用,那就本身寫 loader 去加上這個源碼路徑,這裏採用了 Custom Block。
這個方法是慢慢調試自定義的 loader 摸索出來的,先在 vue-loader 以前加自定義的 loader A, 去注入 Custom Block 代碼,再在全局加入一個針對該 Custom Block 的 loader B。
這裏將方案封裝,在 chainWebpack 中調用便可:
/* vue.config.js */
const VueFilenameInjector = require('./path/to/vue-filename-injector')
module.exports = {
chainWebpack: config => {
VueFilenameInjector(config, {
propName: '__source' // default
})
}
}
複製代碼
源碼參考:@d2-projects/d2-advance/tools/vue-filename-injector
.
└── vue-filename-injector
├── README.md
├── index.js
└── src
├── index.js
└── lib
├── config.js
├── injector.js
└── loader.js
複製代碼
vue-filename-injector/index.js
:
const { blockName } = require('./lib/config.js')
// for chainWebpack
module.exports = function(config, options) {
// 注入
config.module
.rule('vue')
.use('vue-filename-injector')
.loader(require.resolve('./lib/injector.js'))
.options(options)
.after('vue-loader') // 不知爲什麼 .before() 不是預期的結果,這裏就繞了一下
.end()
// 解析
config.module
.rule('')
.resourceQuery(new RegExp(`blockType=${blockName}`))
.use('vue-filename-injector-loader')
.loader(require.resolve('./lib/loader.js'))
.end()
}
複製代碼
vue-filename-injector/lib/config.js
:
const defaultPropName = '__source'
const blockName = 'vue-filename-injector'
module.exports = {
defaultPropName,
blockName
}
複製代碼
vue-filename-injector/lib/injector.js
,源碼部分來自 vue-loader
:
const path = require('path')
const loaderUtils = require('loader-utils')
const { blockName, defaultPropName } = require('./config.js')
module.exports = function (content /*, map, meta */) {
const loaderContext = this
const {
rootContext,
resourcePath
} = loaderContext
const context = rootContext || process.cwd()
const options = loaderUtils.getOptions(loaderContext) || {}
const rawShortFilePath = path
.relative(context, resourcePath)
.replace(/^(\.\.[\/\\])+/, '')
const propName = options.propName || defaultPropName
content += ` <${blockName}> export default function (Component) { Component.options.${propName} = ${JSON.stringify(rawShortFilePath.replace(/\\/g, '/'))} } </${blockName}> `
return content
}
複製代碼
vue-filename-injector/lib/loader.js
:
module.exports = function(source, map) {
this.callback(null, source, map)
}
複製代碼
可進入預覽頁面查看效果,在右下角有 Toggle
github.com/d2-projects… (可能還在翻車中)
目前看來,用自定義 loader 去注入代碼是最便捷的方案,不用在每一個組件中手寫重複的代碼。 因爲 vue-cli3 採用 chainWebpack,加上我的對 webpack 的理解更是不深,暫時採用方案 3。