Look at the original

The problem starts with an idea.

D2Admin is an open source, front-end, background integration solution, originally based on VUE-CLI2, probably to vuE-CLI3 transition, the author Lao Li, want to add a Toggle in the bottom right corner of the page, jump to the current page source corresponding github page.

D2Admin Demo page too many, want to see the source of a page, for not familiar with the project directory structure of the novice is very unfriendly.

These pages are unified as.vue components, so transform: how to obtain the vue single file’s own source path?

Three options have been worked out so far, with the ultimate goal of assigning its own path to this.$options.__source. Option 3 is the latest.

Option 1: node + __filename

Use the __filename variable in node directly:

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

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

Since webpack builds the source file internally into the Node module, the script contents of the.vue file are also converted, where __filename is run at compile time, directly to the current file’s own path.

Using this variable also requires node.__filename to be enabled in the Webpack configuration:

/* vue.config.js */
module.exports = {
  // ...
  chainWebpack: config= > {
    // ...
    config.node
      .set('__dirname'.true) / / in the same way
      .set('__filename'.true)}};Copy the code

disadvantages

  • To manually assign values to each component, mixins are not yet available
  • __filenameThe resulting path is in the section.vueFile is not accurate, and the path may also have queryString attached

In the beginning, tough Old Li used this method to manually attach paths to hundreds of components, but it was better than writing down every path manually

Scheme 2: Vue-loader + exposeFilename

At the loader level, vue-loader provides an exposeFilename option. When enabled, this.$options.__file will be given to each.vue component with the exact path.

Loader configuration:

/* vue.config.js */
module.exports = {
  // ...
  chainWebpack: config= > {
    // ...
    config.module
      .rule('vue')
      .use('vue-loader')
        .loader('vue-loader')
        .tap(options= > {
          options.exposeFilename = true
          return options
        })
  }
};
Copy the code

ExposeFilename is enabled by default in the development environment.

This.$options.__file, roughly SRC /foo/bar.vue.

disadvantages

  • For the sake of safety.vue-loaderIn the production environment will__fileAssign filename, not pathname,See the documentation for

Later, knowing this method, Lao Li changed the code immediately and deleted all the additional codes in plan 1. As a result, he found that the results of production environment were inconsistent and orZ was overturned

Solution 3: Loader + Custom Block

Since solution 2 is not allowed to be used in the production environment, then write loader to add this source path, here uses the Custom Block.

This method is slowly debugging the Custom loader to find out, first add Custom loader A before vue-loader, to inject Custom Block code, and then globally add A loader B for the Custom Block.

Here we encapsulate the scheme and call it in chainWebpack:

/* vue.config.js */
const VueFilenameInjector = require('./path/to/vue-filename-injector')

module.exports = {
  chainWebpack: config= > {
    VueFilenameInjector(config, {
      propName: '__source' // default}}})Copy the code

@d2-projects/d2-advance/tools/ vue-filename-Injector

. └ ─ ─ vue - filename - injector ├ ─ ─ the README. Md ├ ─ ─ index. The js └ ─ ─ the SRC ├ ─ ─ index. The js └ ─ ─ lib ├ ─ ─ config. Js ├ ─ ─ injector. Js └ ─ ─ loader.jsCopy the code

Vue – filename – injector/index. Js:

const { blockName } = require('./lib/config.js')

// for chainWebpack
module.exports = function(config, options) {
  / / injection
  config.module
    .rule('vue')
    .use('vue-filename-injector')
    .loader(require.resolve('./lib/injector.js'))
    .options(options)
    .after('vue-loader') // For some reason.before() is not the expected result
    .end()
  / / parsing
  config.module
    .rule(' ')
    .resourceQuery(new RegExp(`blockType=${blockName}`))
    .use('vue-filename-injector-loader')
    .loader(require.resolve('./lib/loader.js'))
    .end()
}
Copy the code

Vue – filename – injector/lib/config. Js:

const defaultPropName = '__source'
const blockName = 'vue-filename-injector'

module.exports = {
  defaultPropName,
  blockName
}
Copy the code

Vue-filename-injector /lib/ Injector.js; vue-filename-injector/lib/ Injector.js;

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} = The ${JSON.stringify(rawShortFilePath.replace(/\\/g.'/'))}} < /${blockName}>
`
  return content
}
Copy the code

Vue – filename – injector/lib/loader. The js:

module.exports = function(source, map) {
  this.callback(null, source, map)
}
Copy the code

Related to the warehouse

You can enter the preview page to see the effect, and there is Toggle in the lower right corner

Github.com/d2-projects… (Probably still rolling over)

Github.com/d2-projects…

conclusion

Currently, using a custom loader to inject code is the most convenient solution, without having to manually duplicate the code in each component. Since vue-cli3 adopts chainWebpack and my personal understanding of webpack is not deep, scheme 3 is adopted temporarily.