You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
5.8 KiB
218 lines
5.8 KiB
1 month ago
|
const path = require('path')
|
||
|
const hash = require('hash-sum')
|
||
|
const { semver, matchesPluginId } = require('@vue/cli-shared-utils')
|
||
|
|
||
|
// Note: if a plugin-registered command needs to run in a specific default mode,
|
||
|
// the plugin needs to expose it via `module.exports.defaultModes` in the form
|
||
|
// of { [commandName]: mode }. This is because the command mode needs to be
|
||
|
// known and applied before loading user options / applying plugins.
|
||
|
|
||
|
class PluginAPI {
|
||
|
/**
|
||
|
* @param {string} id - Id of the plugin.
|
||
|
* @param {Service} service - A vue-cli-service instance.
|
||
|
*/
|
||
|
constructor (id, service) {
|
||
|
this.id = id
|
||
|
this.service = service
|
||
|
}
|
||
|
|
||
|
get version () {
|
||
|
return require('../package.json').version
|
||
|
}
|
||
|
|
||
|
assertVersion (range) {
|
||
|
if (typeof range === 'number') {
|
||
|
if (!Number.isInteger(range)) {
|
||
|
throw new Error('Expected string or integer value.')
|
||
|
}
|
||
|
range = `^${range}.0.0-0`
|
||
|
}
|
||
|
if (typeof range !== 'string') {
|
||
|
throw new Error('Expected string or integer value.')
|
||
|
}
|
||
|
|
||
|
if (semver.satisfies(this.version, range, { includePrerelease: true })) return
|
||
|
|
||
|
throw new Error(
|
||
|
`Require @vue/cli-service "${range}", but was loaded with "${this.version}".`
|
||
|
)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Current working directory.
|
||
|
*/
|
||
|
getCwd () {
|
||
|
return this.service.context
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resolve path for a project.
|
||
|
*
|
||
|
* @param {string} _path - Relative path from project root
|
||
|
* @return {string} The resolved absolute path.
|
||
|
*/
|
||
|
resolve (_path) {
|
||
|
return path.resolve(this.service.context, _path)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if the project has a given plugin.
|
||
|
*
|
||
|
* @param {string} id - Plugin id, can omit the (@vue/|vue-|@scope/vue)-cli-plugin- prefix
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
hasPlugin (id) {
|
||
|
return this.service.plugins.some(p => matchesPluginId(id, p.id))
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a command that will become available as `vue-cli-service [name]`.
|
||
|
*
|
||
|
* @param {string} name
|
||
|
* @param {object} [opts]
|
||
|
* {
|
||
|
* description: string,
|
||
|
* usage: string,
|
||
|
* options: { [string]: string }
|
||
|
* }
|
||
|
* @param {function} fn
|
||
|
* (args: { [string]: string }, rawArgs: string[]) => ?Promise
|
||
|
*/
|
||
|
registerCommand (name, opts, fn) {
|
||
|
if (typeof opts === 'function') {
|
||
|
fn = opts
|
||
|
opts = null
|
||
|
}
|
||
|
this.service.commands[name] = { fn, opts: opts || {} }
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a function that will receive a chainable webpack config
|
||
|
* the function is lazy and won't be called until `resolveWebpackConfig` is
|
||
|
* called
|
||
|
*
|
||
|
* @param {function} fn
|
||
|
*/
|
||
|
chainWebpack (fn) {
|
||
|
this.service.webpackChainFns.push(fn)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register
|
||
|
* - a webpack configuration object that will be merged into the config
|
||
|
* OR
|
||
|
* - a function that will receive the raw webpack config.
|
||
|
* the function can either mutate the config directly or return an object
|
||
|
* that will be merged into the config.
|
||
|
*
|
||
|
* @param {object | function} fn
|
||
|
*/
|
||
|
configureWebpack (fn) {
|
||
|
this.service.webpackRawConfigFns.push(fn)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register a dev serve config function. It will receive the express `app`
|
||
|
* instance of the dev server.
|
||
|
*
|
||
|
* @param {function} fn
|
||
|
*/
|
||
|
configureDevServer (fn) {
|
||
|
this.service.devServerConfigFns.push(fn)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resolve the final raw webpack config, that will be passed to webpack.
|
||
|
*
|
||
|
* @param {ChainableWebpackConfig} [chainableConfig]
|
||
|
* @return {object} Raw webpack config.
|
||
|
*/
|
||
|
resolveWebpackConfig (chainableConfig) {
|
||
|
return this.service.resolveWebpackConfig(chainableConfig)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resolve an intermediate chainable webpack config instance, which can be
|
||
|
* further tweaked before generating the final raw webpack config.
|
||
|
* You can call this multiple times to generate different branches of the
|
||
|
* base webpack config.
|
||
|
* See https://github.com/mozilla-neutrino/webpack-chain
|
||
|
*
|
||
|
* @return {ChainableWebpackConfig}
|
||
|
*/
|
||
|
resolveChainableWebpackConfig () {
|
||
|
return this.service.resolveChainableWebpackConfig()
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generate a cache identifier from a number of variables
|
||
|
*/
|
||
|
genCacheConfig (id, partialIdentifier, configFiles = []) {
|
||
|
const fs = require('fs')
|
||
|
const cacheDirectory = this.resolve(`node_modules/.cache/${id}`)
|
||
|
|
||
|
// replace \r\n to \n generate consistent hash
|
||
|
const fmtFunc = conf => {
|
||
|
if (typeof conf === 'function') {
|
||
|
return conf.toString().replace(/\r\n?/g, '\n')
|
||
|
}
|
||
|
return conf
|
||
|
}
|
||
|
|
||
|
const variables = {
|
||
|
partialIdentifier,
|
||
|
'cli-service': require('../package.json').version,
|
||
|
env: process.env.NODE_ENV,
|
||
|
test: !!process.env.VUE_CLI_TEST,
|
||
|
config: [
|
||
|
fmtFunc(this.service.projectOptions.chainWebpack),
|
||
|
fmtFunc(this.service.projectOptions.configureWebpack)
|
||
|
]
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
variables['cache-loader'] = require('cache-loader/package.json').version
|
||
|
} catch (e) {
|
||
|
// cache-loader is only intended to be used for webpack 4
|
||
|
}
|
||
|
|
||
|
if (!Array.isArray(configFiles)) {
|
||
|
configFiles = [configFiles]
|
||
|
}
|
||
|
configFiles = configFiles.concat([
|
||
|
'package-lock.json',
|
||
|
'yarn.lock',
|
||
|
'pnpm-lock.yaml'
|
||
|
])
|
||
|
|
||
|
const readConfig = file => {
|
||
|
const absolutePath = this.resolve(file)
|
||
|
if (!fs.existsSync(absolutePath)) {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (absolutePath.endsWith('.js')) {
|
||
|
// should evaluate config scripts to reflect environment variable changes
|
||
|
try {
|
||
|
return JSON.stringify(require(absolutePath))
|
||
|
} catch (e) {
|
||
|
return fs.readFileSync(absolutePath, 'utf-8')
|
||
|
}
|
||
|
} else {
|
||
|
return fs.readFileSync(absolutePath, 'utf-8')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
variables.configFiles = configFiles.map(file => {
|
||
|
const content = readConfig(file)
|
||
|
return content && content.replace(/\r\n?/g, '\n')
|
||
|
})
|
||
|
|
||
|
const cacheIdentifier = hash(variables)
|
||
|
return { cacheDirectory, cacheIdentifier }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = PluginAPI
|