专栏首页今日前端你知道如何获取 vue 组件自身源码路径吗?

你知道如何获取 vue 组件自身源码路径吗?

作者:D2Projects 成员 | Aysnine

点击文章末尾查看原文跳转至作者原网页


这个问题要从一个想法说起。

D2Admin[1] 是一个开源的,前端中后台集成方案,原先是基于 vue-cli2,大概是向 vue-cli3 过渡时, 作者老李,想在页面右下角加个 Toggle 点击,跳到当前页面源码对应的 github 页面。

确实很实用的功能,D2Admin 的 Demo 页面太多了,想看某个页面的源码,对于不熟悉项目目录结构的新手很不友好。

这些页面统一为 .vue 组件,那么转换一下:如何获取 vue 单文件自身源码路径?

目前经历了三个方案,最终目标是把自身路径赋值到 this.$options.__source 上。目前方案 3 是最新的。

方案 1 :node + __filename

直接使用 node 中的 __filename 变量:

<template>
  <h1>{{ $options.__source }}</h1>
</template>

<script>
export default {
  mounted() {
    this.$options.__source = __filename
  }
}
</script>

因为 webpack 编译时,会把源码文件在内部转为 node 模块,.vue 文件中的 script 内容也被转换了, 其中的 __filename 在编译时被运行,直接得到当前文件自身路径。

使用这个变量还需要在 webpack 配置中启用 node.__filename

/* vue.config.js */
module.exports = {
  // ...
  chainWebpack: config => {
    // ...
    config.node
      .set('__dirname', true) // 同理
      .set('__filename', true)
  }
};

缺点

•要在每个组件里手动赋值,还不能用 mixin•__filename 得到的路径在部分 .vue 文件下并不准确,路径可能还会带附带 querystring

一开始,坚强的老李用这个方式,给上百个组件手动挂上了路径,但总比手动写死每个路径要好

方案 2 :vue-loader + exposeFilename

在 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 赋值为文件名,非路径名,详见文档[2]

后来得知这个方法,老李就第一时间改代码,删了方案 1 中的所有附加代码,结果发现生产环境结果不一致,翻车了orz

方案 3 :loader + Custom Block

既然方案 2 不让在生产环境用,那就自己写 loader 去加上这个源码路径,这里采用了 Custom Block[3]。

这个方法是慢慢调试自定义的 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[4]

.
└── 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

https://github.com/d2-projects/d2-admin

https://github.com/d2-projects/d2-advance

总结

目前看来,用自定义 loader 去注入代码是最便捷的方案,不用在每个组件中手写重复的代码。 由于 vue-cli3 采用 chainWebpack,加上个人对 webpack 的理解更是不深,暂时采用方案 3。

本文分享自微信公众号 - 今日前端(js-now),作者:@Aysnine

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • D2Admin 1.11.0

    此次更新主要升级了 mock 网络请求的方式,新的方案使用 axios + faker 实现网络请求拦截。并且支持在配置接口的位置同时配置 mock,同时接口配...

    FairyEver
  • 你知道 JavaScript 有 535 种方法刷新页面吗?

    FairyEver
  • 活用控制反转 -- 一大波骚操作

    在我初学编程的时候,还没写过完整点的项目就看过了一些高阶概念。在没有实践时,这些概念的神奇和强大之处很难被完全体会的。而一旦自己在摸索中应用了,瞬间觉得打开了一...

    FairyEver
  • 一张图教你快速玩转vue-cli3

    本文系统的梳理了vue-cli3搭建项目的常见用法,目的在于让你快速掌握独立搭建vue项目的能力。你将会了解如下知识点:

    徐小夕
  • 一张图教你快速玩转vue-cli3

    本文系统的梳理了vue-cli3搭建项目的常见用法,目的在于让你快速掌握独立搭建vue项目的能力。你将会了解如下知识点:

    徐小夕
  • (总结)Nginx/LVS/HAProxy负载均衡软件的优缺点详解

    PS:Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件,本人都在多个项目中实施过,参考了一些资料,结合自己的一些使用经验,总结一下。 一般对...

    小小科
  • Nginx/LVS/HAProxy负载均衡软件的优缺点详解

    PS:Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件,本人都在多个项目中实施过,参考了一些资料,结合自己的一些使用经验,总结一下。 一般对...

    用户1263954
  • (总结)Nginx/LVS/HAProxy负载均衡软件的优缺点详解

    PS:Nginx/LVS/HAProxy是目前使用最广泛的三种负载均衡软件,本人都在多个项目中实施过,参考了一些资料,结合自己的一些使用经验,总结一下。

    用户5640963
  • Nginx/LVS/HAProxy 负载均衡软件的优缺点详解(转自云栖社区)

    老七Linux
  • numpy 的排序

    py3study

扫码关注云+社区

领取腾讯云代金券