PluginAPI.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. const path = require('path')
  2. const hash = require('hash-sum')
  3. const semver = require('semver')
  4. const { matchesPluginId } = require('@vue/cli-shared-utils')
  5. // Note: if a plugin-registered command needs to run in a specific default mode,
  6. // the plugin needs to expose it via `module.exports.defaultModes` in the form
  7. // of { [commandName]: mode }. This is because the command mode needs to be
  8. // known and applied before loading user options / applying plugins.
  9. class PluginAPI {
  10. /**
  11. * @param {string} id - Id of the plugin.
  12. * @param {Service} service - A vue-cli-service instance.
  13. */
  14. constructor (id, service) {
  15. this.id = id
  16. this.service = service
  17. }
  18. get version () {
  19. return require('../package.json').version
  20. }
  21. assertVersion (range) {
  22. if (typeof range === 'number') {
  23. console.log(range, Number.isInteger(range))
  24. if (!Number.isInteger(range)) {
  25. throw new Error('Expected string or integer value.')
  26. }
  27. range = `^${range}.0.0-0`
  28. }
  29. if (typeof range !== 'string') {
  30. throw new Error('Expected string or integer value.')
  31. }
  32. if (semver.satisfies(this.version, range)) return
  33. throw new Error(
  34. `Require @vue/cli-service "${range}", but was loaded with "${this.version}".`
  35. )
  36. }
  37. /**
  38. * Current working directory.
  39. */
  40. getCwd () {
  41. return this.service.context
  42. }
  43. /**
  44. * Resolve path for a project.
  45. *
  46. * @param {string} _path - Relative path from project root
  47. * @return {string} The resolved absolute path.
  48. */
  49. resolve (_path) {
  50. return path.resolve(this.service.context, _path)
  51. }
  52. /**
  53. * Check if the project has a given plugin.
  54. *
  55. * @param {string} id - Plugin id, can omit the (@vue/|vue-|@scope/vue)-cli-plugin- prefix
  56. * @return {boolean}
  57. */
  58. hasPlugin (id) {
  59. if (id === 'router') id = 'vue-router'
  60. if (['vue-router', 'vuex'].includes(id)) {
  61. const pkg = this.service.pkg
  62. return ((pkg.dependencies && pkg.dependencies[id]) || (pkg.devDependencies && pkg.devDependencies[id]))
  63. }
  64. return this.service.plugins.some(p => matchesPluginId(id, p.id))
  65. }
  66. /**
  67. * Register a command that will become available as `vue-cli-service [name]`.
  68. *
  69. * @param {string} name
  70. * @param {object} [opts]
  71. * {
  72. * description: string,
  73. * usage: string,
  74. * options: { [string]: string }
  75. * }
  76. * @param {function} fn
  77. * (args: { [string]: string }, rawArgs: string[]) => ?Promise
  78. */
  79. registerCommand (name, opts, fn) {
  80. if (typeof opts === 'function') {
  81. fn = opts
  82. opts = null
  83. }
  84. this.service.commands[name] = { fn, opts: opts || {}}
  85. }
  86. /**
  87. * Register a function that will receive a chainable webpack config
  88. * the function is lazy and won't be called until `resolveWebpackConfig` is
  89. * called
  90. *
  91. * @param {function} fn
  92. */
  93. chainWebpack (fn) {
  94. this.service.webpackChainFns.push(fn)
  95. }
  96. /**
  97. * Register
  98. * - a webpack configuration object that will be merged into the config
  99. * OR
  100. * - a function that will receive the raw webpack config.
  101. * the function can either mutate the config directly or return an object
  102. * that will be merged into the config.
  103. *
  104. * @param {object | function} fn
  105. */
  106. configureWebpack (fn) {
  107. this.service.webpackRawConfigFns.push(fn)
  108. }
  109. /**
  110. * Register a dev serve config function. It will receive the express `app`
  111. * instance of the dev server.
  112. *
  113. * @param {function} fn
  114. */
  115. configureDevServer (fn) {
  116. this.service.devServerConfigFns.push(fn)
  117. }
  118. /**
  119. * Resolve the final raw webpack config, that will be passed to webpack.
  120. *
  121. * @param {ChainableWebpackConfig} [chainableConfig]
  122. * @return {object} Raw webpack config.
  123. */
  124. resolveWebpackConfig (chainableConfig) {
  125. return this.service.resolveWebpackConfig(chainableConfig)
  126. }
  127. /**
  128. * Resolve an intermediate chainable webpack config instance, which can be
  129. * further tweaked before generating the final raw webpack config.
  130. * You can call this multiple times to generate different branches of the
  131. * base webpack config.
  132. * See https://github.com/mozilla-neutrino/webpack-chain
  133. *
  134. * @return {ChainableWebpackConfig}
  135. */
  136. resolveChainableWebpackConfig () {
  137. return this.service.resolveChainableWebpackConfig()
  138. }
  139. /**
  140. * Generate a cache identifier from a number of variables
  141. */
  142. genCacheConfig (id, partialIdentifier, configFiles = []) {
  143. const fs = require('fs')
  144. const cacheDirectory = this.resolve(`node_modules/.cache/${id}`)
  145. // replace \r\n to \n generate consistent hash
  146. const fmtFunc = conf => {
  147. if (typeof conf === 'function') {
  148. return conf.toString().replace(/\r\n?/g, '\n')
  149. }
  150. return conf
  151. }
  152. const variables = {
  153. partialIdentifier,
  154. 'cli-service': require('../package.json').version,
  155. 'cache-loader': require('cache-loader/package.json').version,
  156. env: process.env.NODE_ENV,
  157. test: !!process.env.VUE_CLI_TEST,
  158. config: [
  159. fmtFunc(this.service.projectOptions.chainWebpack),
  160. fmtFunc(this.service.projectOptions.configureWebpack)
  161. ]
  162. }
  163. if (!Array.isArray(configFiles)) {
  164. configFiles = [configFiles]
  165. }
  166. configFiles = configFiles.concat([
  167. 'package-lock.json',
  168. 'yarn.lock',
  169. 'pnpm-lock.yaml'
  170. ])
  171. const readConfig = file => {
  172. const absolutePath = this.resolve(file)
  173. if (!fs.existsSync(absolutePath)) {
  174. return
  175. }
  176. if (absolutePath.endsWith('.js')) {
  177. // should evaluate config scripts to reflect environment variable changes
  178. try {
  179. return JSON.stringify(require(absolutePath))
  180. } catch (e) {
  181. return fs.readFileSync(absolutePath, 'utf-8')
  182. }
  183. } else {
  184. return fs.readFileSync(absolutePath, 'utf-8')
  185. }
  186. }
  187. for (const file of configFiles) {
  188. const content = readConfig(file)
  189. if (content) {
  190. variables.configFiles = content.replace(/\r\n?/g, '\n')
  191. break
  192. }
  193. }
  194. const cacheIdentifier = hash(variables)
  195. return { cacheDirectory, cacheIdentifier }
  196. }
  197. }
  198. module.exports = PluginAPI