vue hello world项目
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.

377 lines
12 KiB

3 years ago
const path = require('path')
const debug = require('debug')
const { merge } = require('webpack-merge')
const Config = require('webpack-chain')
const PluginAPI = require('./PluginAPI')
const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
const defaultsDeep = require('lodash.defaultsdeep')
const { warn, error, isPlugin, resolvePluginId, loadModule, resolvePkg, resolveModule, sortPlugins } = require('@vue/cli-shared-utils')
const { defaults } = require('./options')
const loadFileConfig = require('./util/loadFileConfig')
const resolveUserConfig = require('./util/resolveUserConfig')
// Seems we can't use `instanceof Promise` here (would fail the tests)
const isPromise = p => p && typeof p.then === 'function'
module.exports = class Service {
constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
process.VUE_CLI_SERVICE = this
this.initialized = false
this.context = context
this.inlineOptions = inlineOptions
this.webpackChainFns = []
this.webpackRawConfigFns = []
this.devServerConfigFns = []
this.commands = {}
// Folder containing the target package.json for plugins
this.pkgContext = context
// package.json containing the plugins
this.pkg = this.resolvePkg(pkg)
// If there are inline plugins, they will be used instead of those
// found in package.json.
// When useBuiltIn === false, built-in plugins are disabled. This is mostly
// for testing.
this.plugins = this.resolvePlugins(plugins, useBuiltIn)
// pluginsToSkip will be populated during run()
this.pluginsToSkip = new Set()
// resolve the default mode to use for each command
// this is provided by plugins as module.exports.defaultModes
// so we can get the information without actually applying the plugin.
this.modes = this.plugins.reduce((modes, { apply: { defaultModes } }) => {
return Object.assign(modes, defaultModes)
}, {})
}
resolvePkg (inlinePkg, context = this.context) {
if (inlinePkg) {
return inlinePkg
}
const pkg = resolvePkg(context)
if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) {
this.pkgContext = path.resolve(context, pkg.vuePlugins.resolveFrom)
return this.resolvePkg(null, this.pkgContext)
}
return pkg
}
init (mode = process.env.VUE_CLI_MODE) {
if (this.initialized) {
return
}
this.initialized = true
this.mode = mode
// load mode .env
if (mode) {
this.loadEnv(mode)
}
// load base .env
this.loadEnv()
// load user config
const userOptions = this.loadUserOptions()
const loadedCallback = (loadedUserOptions) => {
this.projectOptions = defaultsDeep(loadedUserOptions, defaults())
debug('vue:project-config')(this.projectOptions)
// apply plugins.
this.plugins.forEach(({ id, apply }) => {
if (this.pluginsToSkip.has(id)) return
apply(new PluginAPI(id, this), this.projectOptions)
})
// apply webpack configs from project config file
if (this.projectOptions.chainWebpack) {
this.webpackChainFns.push(this.projectOptions.chainWebpack)
}
if (this.projectOptions.configureWebpack) {
this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
}
}
if (isPromise(userOptions)) {
return userOptions.then(loadedCallback)
} else {
return loadedCallback(userOptions)
}
}
loadEnv (mode) {
const logger = debug('vue:env')
const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`)
const localPath = `${basePath}.local`
const load = envPath => {
try {
const env = dotenv.config({ path: envPath, debug: process.env.DEBUG })
dotenvExpand(env)
logger(envPath, env)
} catch (err) {
// only ignore error if file is not found
if (err.toString().indexOf('ENOENT') < 0) {
error(err)
}
}
}
load(localPath)
load(basePath)
// by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
// is production or test. However the value in .env files will take higher
// priority.
if (mode) {
// always set NODE_ENV during tests
// as that is necessary for tests to not be affected by each other
const shouldForceDefaultEnv = (
process.env.VUE_CLI_TEST &&
!process.env.VUE_CLI_TEST_TESTING_ENV
)
const defaultNodeEnv = (mode === 'production' || mode === 'test')
? mode
: 'development'
if (shouldForceDefaultEnv || process.env.NODE_ENV == null) {
process.env.NODE_ENV = defaultNodeEnv
}
if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) {
process.env.BABEL_ENV = defaultNodeEnv
}
}
}
setPluginsToSkip (args, rawArgv) {
let skipPlugins = args['skip-plugins']
const pluginsToSkip = new Set()
if (skipPlugins) {
// When only one appearence, convert to array to prevent duplicate code
if (!Array.isArray(skipPlugins)) {
skipPlugins = Array.from([skipPlugins])
}
// Iter over all --skip-plugins appearences
for (const value of skipPlugins.values()) {
for (const plugin of value.split(',').map(id => resolvePluginId(id))) {
pluginsToSkip.add(plugin)
}
}
}
this.pluginsToSkip = pluginsToSkip
delete args['skip-plugins']
// Delete all --skip-plugin appearences
let index
while ((index = rawArgv.indexOf('--skip-plugins')) > -1) {
rawArgv.splice(index, 2) // Remove the argument and its value
}
}
resolvePlugins (inlinePlugins, useBuiltIn) {
const idToPlugin = (id, absolutePath) => ({
id: id.replace(/^.\//, 'built-in:'),
apply: require(absolutePath || id)
})
let plugins
const builtInPlugins = [
'./commands/serve',
'./commands/build',
'./commands/inspect',
'./commands/help',
// config plugins are order sensitive
'./config/base',
'./config/assets',
'./config/css',
'./config/prod',
'./config/app'
].map((id) => idToPlugin(id))
if (inlinePlugins) {
plugins = useBuiltIn !== false
? builtInPlugins.concat(inlinePlugins)
: inlinePlugins
} else {
const projectPlugins = Object.keys(this.pkg.devDependencies || {})
.concat(Object.keys(this.pkg.dependencies || {}))
.filter(isPlugin)
.map(id => {
if (
this.pkg.optionalDependencies &&
id in this.pkg.optionalDependencies
) {
let apply = loadModule(id, this.pkgContext)
if (!apply) {
warn(`Optional dependency ${id} is not installed.`)
apply = () => {}
}
return { id, apply }
} else {
return idToPlugin(id, resolveModule(id, this.pkgContext))
}
})
plugins = builtInPlugins.concat(projectPlugins)
}
// Local plugins
if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
const files = this.pkg.vuePlugins.service
if (!Array.isArray(files)) {
throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
}
plugins = plugins.concat(files.map(file => ({
id: `local:${file}`,
apply: loadModule(`./${file}`, this.pkgContext)
})))
}
debug('vue:plugins')(plugins)
const orderedPlugins = sortPlugins(plugins)
debug('vue:plugins-ordered')(orderedPlugins)
return orderedPlugins
}
async run (name, args = {}, rawArgv = []) {
// resolve mode
// prioritize inline --mode
// fallback to resolved default modes from plugins or development if --watch is defined
const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])
// --skip-plugins arg may have plugins that should be skipped during init()
this.setPluginsToSkip(args, rawArgv)
// load env variables, load user config, apply plugins
await this.init(mode)
args._ = args._ || []
let command = this.commands[name]
if (!command && name) {
error(`command "${name}" does not exist.`)
process.exit(1)
}
if (!command || args.help || args.h) {
command = this.commands.help
} else {
args._.shift() // remove command itself
rawArgv.shift()
}
const { fn } = command
return fn(args, rawArgv)
}
resolveChainableWebpackConfig () {
const chainableConfig = new Config()
// apply chains
this.webpackChainFns.forEach(fn => fn(chainableConfig))
return chainableConfig
}
resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) {
if (!this.initialized) {
throw new Error('Service must call init() before calling resolveWebpackConfig().')
}
// get raw config
let config = chainableConfig.toConfig()
const original = config
// apply raw config fns
this.webpackRawConfigFns.forEach(fn => {
if (typeof fn === 'function') {
// function with optional return value
const res = fn(config)
if (res) config = merge(config, res)
} else if (fn) {
// merge literal values
config = merge(config, fn)
}
})
// #2206 If config is merged by merge-webpack, it discards the __ruleNames
// information injected by webpack-chain. Restore the info so that
// vue inspect works properly.
if (config !== original) {
cloneRuleNames(
config.module && config.module.rules,
original.module && original.module.rules
)
}
// check if the user has manually mutated output.publicPath
const target = process.env.VUE_CLI_BUILD_TARGET
if (
!process.env.VUE_CLI_TEST &&
(target && target !== 'app') &&
config.output.publicPath !== this.projectOptions.publicPath
) {
throw new Error(
`Do not modify webpack output.publicPath directly. ` +
`Use the "publicPath" option in vue.config.js instead.`
)
}
if (
!process.env.VUE_CLI_ENTRY_FILES &&
typeof config.entry !== 'function'
) {
let entryFiles
if (typeof config.entry === 'string') {
entryFiles = [config.entry]
} else if (Array.isArray(config.entry)) {
entryFiles = config.entry
} else {
entryFiles = Object.values(config.entry || []).reduce((allEntries, curr) => {
return allEntries.concat(curr)
}, [])
}
entryFiles = entryFiles.map(file => path.resolve(this.context, file))
process.env.VUE_CLI_ENTRY_FILES = JSON.stringify(entryFiles)
}
return config
}
// Note: we intentionally make this function synchronous by default
// because eslint-import-resolver-webpack does not support async webpack configs.
loadUserOptions () {
const { fileConfig, fileConfigPath } = loadFileConfig(this.context)
if (isPromise(fileConfig)) {
return fileConfig
.then(mod => mod.default)
.then(loadedConfig => resolveUserConfig({
inlineOptions: this.inlineOptions,
pkgConfig: this.pkg.vue,
fileConfig: loadedConfig,
fileConfigPath
}))
}
return resolveUserConfig({
inlineOptions: this.inlineOptions,
pkgConfig: this.pkg.vue,
fileConfig,
fileConfigPath
})
}
}
function cloneRuleNames (to, from) {
if (!to || !from) {
return
}
from.forEach((r, i) => {
if (to[i]) {
Object.defineProperty(to[i], '__ruleNames', {
value: r.__ruleNames
})
cloneRuleNames(to[i].oneOf, r.oneOf)
}
})
}
/** @type {import('../types/index').defineConfig} */
module.exports.defineConfig = (config) => config