PluginAPI.js 5.7 KB

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