first commit

This commit is contained in:
monjack
2025-06-20 18:01:48 +08:00
commit 6daa6d65c1
24611 changed files with 2512443 additions and 0 deletions

217
app_vue/node_modules/@vue/cli-service/lib/PluginAPI.js generated vendored Normal file
View File

@ -0,0 +1,217 @@
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

376
app_vue/node_modules/@vue/cli-service/lib/Service.js generated vendored Normal file
View File

@ -0,0 +1,376 @@
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

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title><%- htmlWebpackPlugin.options.libName %> demo</title>
<script src="./<%- htmlWebpackPlugin.options.assetsFileName %>.umd.js"></script>
<% if (htmlWebpackPlugin.options.cssExtract) { %>
<link rel="stylesheet" href="./<%- htmlWebpackPlugin.options.assetsFileName %>.css">
<% } %>
<script>
console.log(<%- htmlWebpackPlugin.options.libName %>)
</script>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title><%- htmlWebpackPlugin.options.libName %> demo</title>
<script src="//unpkg.com/vue@<%- htmlWebpackPlugin.options.vueMajor %>"></script>
<script src="./<%- htmlWebpackPlugin.options.assetsFileName %>.umd.js"></script>
<% if (htmlWebpackPlugin.options.cssExtract) { %>
<link rel="stylesheet" href="./<%- htmlWebpackPlugin.options.assetsFileName %>.css">
<% } %>
<div id="app">
<demo></demo>
</div>
<script>
<% if (htmlWebpackPlugin.options.vueMajor === 3) { %>
Vue.createApp({
components: {
demo: <%- htmlWebpackPlugin.options.libName %>
}
}).mount('#app')
<% } else { %>
new Vue({
components: {
demo: <%- htmlWebpackPlugin.options.libName %>
}
}).$mount('#app')
<% } %>
</script>

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title><%- htmlWebpackPlugin.options.libName %> demo</title>
<script src="https://unpkg.com/vue@<%- htmlWebpackPlugin.options.vueMajor %>"></script>
<script src="./<%- htmlWebpackPlugin.options.libName %>.js"></script>
<% for (const comp of htmlWebpackPlugin.options.components) { %>
<<%= comp %>></<%= comp %>>
<% } %>

View File

@ -0,0 +1,2 @@
import './setPublicPath'
export * from '~entry'

View File

@ -0,0 +1,4 @@
import './setPublicPath'
import mod from '~entry'
export default mod
export * from '~entry'

View File

@ -0,0 +1,12 @@
import './setPublicPath'
import Vue from 'vue'
import wrap from '@vue/web-component-wrapper'
// runtime shared by every component chunk
import 'css-loader/dist/runtime/api.js'
import 'vue-style-loader/lib/addStylesShadow'
import 'vue-loader/lib/runtime/componentNormalizer'
window.customElements.define('build-wc-async-app', wrap(Vue, () => import('~root/src/App.vue?shadow')))
window.customElements.define('build-wc-async-hello-world', wrap(Vue, () => import('~root/src/components/HelloWorld.vue?shadow')))

View File

@ -0,0 +1,77 @@
module.exports = function formatStats (stats, dir, api) {
const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const ui = require('cliui')({ width: process.stdout.columns || 80 })
const { chalk } = require('@vue/cli-shared-utils')
const json = stats.toJson({
hash: false,
modules: false,
chunks: false
})
let assets = json.assets
? json.assets
: json.children.reduce((acc, child) => acc.concat(child.assets), [])
const seenNames = new Map()
const isJS = val => /\.js$/.test(val)
const isCSS = val => /\.css$/.test(val)
const isMinJS = val => /\.min\.js$/.test(val)
assets = assets
.map(a => {
a.name = a.name.split('?')[0]
return a
})
.filter(a => {
if (seenNames.has(a.name)) {
return false
}
seenNames.set(a.name, true)
return isJS(a.name) || isCSS(a.name)
})
.sort((a, b) => {
if (isJS(a.name) && isCSS(b.name)) return -1
if (isCSS(a.name) && isJS(b.name)) return 1
if (isMinJS(a.name) && !isMinJS(b.name)) return -1
if (!isMinJS(a.name) && isMinJS(b.name)) return 1
return b.size - a.size
})
function formatSize (size) {
return (size / 1024).toFixed(2) + ' KiB'
}
function getGzippedSize (asset) {
const filepath = api.resolve(path.join(dir, asset.name))
const buffer = fs.readFileSync(filepath)
return formatSize(zlib.gzipSync(buffer).length)
}
function makeRow (a, b, c) {
return ` ${a}\t ${b}\t ${c}`
}
ui.div(
makeRow(
chalk.cyan.bold(`File`),
chalk.cyan.bold(`Size`),
chalk.cyan.bold(`Gzipped`)
) + `\n\n` +
assets.map(asset => makeRow(
/js$/.test(asset.name)
? chalk.green(path.join(dir, asset.name))
: chalk.blue(path.join(dir, asset.name)),
formatSize(asset.size),
getGzippedSize(asset)
)).join(`\n`)
)
const time = stats.endTime - stats.startTime
const now = new Date().toISOString()
const hash = stats.hash
const info = `Build at: ${chalk.white(now)} - Hash: ${chalk.white(hash)} - Time: ${chalk.white(time)}ms`
return `${ui.toString()}\n\n ${chalk.gray(`Images and other types of assets omitted.`)}\n ${info}\n`
}

View File

@ -0,0 +1,238 @@
const defaults = {
clean: true,
target: 'app',
module: true,
formats: 'commonjs,umd,umd-min'
}
const buildModes = {
lib: 'library',
wc: 'web component',
'wc-async': 'web component (async)'
}
const modifyConfig = (config, fn) => {
if (Array.isArray(config)) {
config.forEach(c => fn(c))
} else {
fn(config)
}
}
module.exports = (api, options) => {
api.registerCommand('build', {
description: 'build for production',
usage: 'vue-cli-service build [options] [entry|pattern]',
options: {
'--mode': `specify env mode (default: production)`,
'--dest': `specify output directory (default: ${options.outputDir})`,
'--no-module': `build app without generating <script type="module"> chunks for modern browsers`,
'--target': `app | lib | wc | wc-async (default: ${defaults.target})`,
'--inline-vue': 'include the Vue module in the final bundle of library or web component target',
'--formats': `list of output formats for library builds (default: ${defaults.formats})`,
'--name': `name for lib or web-component mode (default: "name" in package.json or entry filename)`,
'--filename': `file name for output, only usable for 'lib' target (default: value of --name)`,
'--no-clean': `do not remove the dist directory contents before building the project`,
'--report': `generate report.html to help analyze bundle content`,
'--report-json': 'generate report.json to help analyze bundle content',
'--skip-plugins': `comma-separated list of plugin names to skip for this run`,
'--watch': `watch for changes`,
'--stdin': `close when stdin ends`
}
}, async (args, rawArgs) => {
for (const key in defaults) {
if (args[key] == null) {
args[key] = defaults[key]
}
}
args.entry = args.entry || args._[0]
if (args.target !== 'app') {
args.entry = args.entry || 'src/App.vue'
}
process.env.VUE_CLI_BUILD_TARGET = args.target
const { log, execa } = require('@vue/cli-shared-utils')
const { allProjectTargetsSupportModule } = require('../../util/targets')
let needsDifferentialLoading = args.target === 'app' && args.module
if (allProjectTargetsSupportModule) {
log(
`All browser targets in the browserslist configuration have supported ES module.\n` +
`Therefore we don't build two separate bundles for differential loading.\n`
)
needsDifferentialLoading = false
}
args.needsDifferentialLoading = needsDifferentialLoading
if (!needsDifferentialLoading) {
await build(args, api, options)
return
}
process.env.VUE_CLI_MODERN_MODE = true
if (!process.env.VUE_CLI_MODERN_BUILD) {
// main-process for legacy build
const legacyBuildArgs = { ...args, moduleBuild: false, keepAlive: true }
await build(legacyBuildArgs, api, options)
// spawn sub-process of self for modern build
const cliBin = require('path').resolve(__dirname, '../../../bin/vue-cli-service.js')
await execa('node', [cliBin, 'build', ...rawArgs], {
stdio: 'inherit',
env: {
VUE_CLI_MODERN_BUILD: true
}
})
} else {
// sub-process for modern build
const moduleBuildArgs = { ...args, moduleBuild: true, clean: false }
await build(moduleBuildArgs, api, options)
}
})
}
async function build (args, api, options) {
const fs = require('fs-extra')
const path = require('path')
const webpack = require('webpack')
const { chalk } = require('@vue/cli-shared-utils')
const formatStats = require('./formatStats')
const validateWebpackConfig = require('../../util/validateWebpackConfig')
const {
log,
done,
info,
logWithSpinner,
stopSpinner
} = require('@vue/cli-shared-utils')
log()
const mode = api.service.mode
if (args.target === 'app') {
const bundleTag = args.needsDifferentialLoading
? args.moduleBuild
? `module bundle `
: `legacy bundle `
: ``
logWithSpinner(`Building ${bundleTag}for ${mode}...`)
} else {
const buildMode = buildModes[args.target]
if (buildMode) {
const additionalParams = buildMode === 'library' ? ` (${args.formats})` : ``
logWithSpinner(`Building for ${mode} as ${buildMode}${additionalParams}...`)
} else {
throw new Error(`Unknown build target: ${args.target}`)
}
}
if (args.dest) {
// Override outputDir before resolving webpack config as config relies on it (#2327)
options.outputDir = args.dest
}
const targetDir = api.resolve(options.outputDir)
const isLegacyBuild = args.needsDifferentialLoading && !args.moduleBuild
// resolve raw webpack config
let webpackConfig
if (args.target === 'lib') {
webpackConfig = require('./resolveLibConfig')(api, args, options)
} else if (
args.target === 'wc' ||
args.target === 'wc-async'
) {
webpackConfig = require('./resolveWcConfig')(api, args, options)
} else {
webpackConfig = require('./resolveAppConfig')(api, args, options)
}
// check for common config errors
validateWebpackConfig(webpackConfig, api, options, args.target)
if (args.watch) {
modifyConfig(webpackConfig, config => {
config.watch = true
})
}
if (args.stdin) {
process.stdin.on('end', () => {
process.exit(0)
})
process.stdin.resume()
}
// Expose advanced stats
if (args.dashboard) {
const DashboardPlugin = require('../../webpack/DashboardPlugin')
modifyConfig(webpackConfig, config => {
config.plugins.push(new DashboardPlugin({
type: 'build',
moduleBuild: args.moduleBuild,
keepAlive: args.keepAlive
}))
})
}
if (args.report || args['report-json']) {
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
modifyConfig(webpackConfig, config => {
const bundleName = args.target !== 'app'
? config.output.filename.replace(/\.js$/, '-')
: isLegacyBuild ? 'legacy-' : ''
config.plugins.push(new BundleAnalyzerPlugin({
logLevel: 'warn',
openAnalyzer: false,
analyzerMode: args.report ? 'static' : 'disabled',
reportFilename: `${bundleName}report.html`,
statsFilename: `${bundleName}report.json`,
generateStatsFile: !!args['report-json']
}))
})
}
if (args.clean) {
await fs.emptyDir(targetDir)
}
return new Promise((resolve, reject) => {
webpack(webpackConfig, (err, stats) => {
stopSpinner(false)
if (err) {
return reject(err)
}
if (stats.hasErrors()) {
return reject(new Error('Build failed with errors.'))
}
if (!args.silent) {
const targetDirShort = path.relative(
api.service.context,
targetDir
)
log(formatStats(stats, targetDirShort, api))
if (args.target === 'app' && !isLegacyBuild) {
if (!args.watch) {
done(`Build complete. The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.`)
info(`Check out deployment instructions at ${chalk.cyan(`https://cli.vuejs.org/guide/deployment.html`)}\n`)
} else {
done(`Build complete. Watching for changes...`)
}
}
}
// test-only signal
if (process.env.VUE_CLI_TEST) {
console.log('Build complete.')
}
resolve()
})
})
}
module.exports.defaultModes = {
build: 'production'
}

View File

@ -0,0 +1,52 @@
module.exports = (api, args, options) => {
// respect inline entry
if (args.entry && !options.pages) {
api.configureWebpack(config => {
config.entry = { app: api.resolve(args.entry) }
})
}
const config = api.resolveChainableWebpackConfig()
const targetDir = api.resolve(args.dest || options.outputDir)
// respect inline build destination in copy plugin
if (args.dest && config.plugins.has('copy')) {
config.plugin('copy').tap(pluginArgs => {
pluginArgs[0].patterns.to = targetDir
return pluginArgs
})
}
if (process.env.VUE_CLI_MODERN_MODE) {
const ModernModePlugin = require('../../webpack/ModernModePlugin')
const SafariNomoduleFixPlugin = require('../../webpack/SafariNomoduleFixPlugin')
if (!args.moduleBuild) {
// Inject plugin to extract build stats and write to disk
config
.plugin('modern-mode-legacy')
.use(ModernModePlugin, [{
targetDir,
isModuleBuild: false
}])
} else {
config
.plugin('safari-nomodule-fix')
.use(SafariNomoduleFixPlugin, [{
// as we may generate an addition file asset (if Safari 10 fix is needed)
// we need to provide the correct directory for that file to place in
jsDirectory: require('../../util/getAssetPath')(options, 'js')
}])
// Inject plugin to read non-modern build stats and inject HTML
config
.plugin('modern-mode-modern')
.use(ModernModePlugin, [{
targetDir,
isModuleBuild: true
}])
}
}
return api.resolveWebpackConfig(config)
}

View File

@ -0,0 +1,159 @@
const fs = require('fs')
const path = require('path')
module.exports = (api, { entry, name, formats, filename, 'inline-vue': inlineVue }, options) => {
const { log, error } = require('@vue/cli-shared-utils')
const abort = msg => {
log()
error(msg)
process.exit(1)
}
const vueMajor = require('../../util/getVueMajor')(api.getCwd())
const fullEntryPath = api.resolve(entry)
if (!fs.existsSync(fullEntryPath)) {
abort(
`Failed to resolve lib entry: ${entry}${entry === `src/App.vue` ? ' (default)' : ''}. ` +
`Make sure to specify the correct entry file.`
)
}
const isVueEntry = /\.vue$/.test(entry)
const libName = (
name ||
(
api.service.pkg.name
? api.service.pkg.name.replace(/^@.+\//, '')
: path.basename(entry).replace(/\.(jsx?|vue)$/, '')
)
)
filename = filename || libName
function genConfig (format, postfix = format, genHTML) {
const config = api.resolveChainableWebpackConfig()
const browserslist = require('browserslist')
const targets = browserslist(undefined, { path: fullEntryPath })
const supportsIE = targets.some(agent => agent.includes('ie'))
const webpack = require('webpack')
config.plugin('need-current-script-polyfill')
.use(webpack.DefinePlugin, [{
'process.env.NEED_CURRENTSCRIPT_POLYFILL': JSON.stringify(supportsIE)
}])
// adjust css output name so they write to the same file
if (config.plugins.has('extract-css')) {
config
.plugin('extract-css')
.tap(args => {
args[0].filename = `${filename}.css`
return args
})
}
// only minify min entry
if (!/\.min/.test(postfix)) {
config.optimization.minimize(false)
}
// inject demo page for umd
if (genHTML) {
const template = isVueEntry ? 'demo-lib.html' : 'demo-lib-js.html'
config
.plugin('demo-html')
.use(require('html-webpack-plugin'), [{
template: path.resolve(__dirname, template),
inject: false,
filename: 'demo.html',
libName,
vueMajor,
assetsFileName: filename,
cssExtract: config.plugins.has('extract-css')
}])
}
// resolve entry/output
const entryName = `${filename}.${postfix}`
config.resolve
.alias
.set('~entry', fullEntryPath)
// set output target before user configureWebpack hooks are applied
config.output.libraryTarget(format)
// set entry/output after user configureWebpack hooks are applied
const rawConfig = api.resolveWebpackConfig(config)
let realEntry = require.resolve('./entry-lib.js')
// avoid importing default if user entry file does not have default export
if (!isVueEntry) {
const entryContent = fs.readFileSync(fullEntryPath, 'utf-8')
if (!/\b(export\s+default|export\s{[^}]+as\s+default)\b/.test(entryContent)) {
realEntry = require.resolve('./entry-lib-no-default.js')
}
}
// externalize Vue in case user imports it
rawConfig.externals = [
...(Array.isArray(rawConfig.externals) ? rawConfig.externals : [rawConfig.externals]),
{
...(inlineVue || {
vue: {
commonjs: 'vue',
commonjs2: 'vue',
root: 'Vue'
}
})
}
].filter(Boolean)
rawConfig.entry = {
[entryName]: realEntry
}
rawConfig.output = Object.assign({
library: libName,
libraryExport: isVueEntry ? 'default' : undefined,
libraryTarget: format,
// preserve UDM header from webpack 3 until webpack provides either
// libraryTarget: 'esm' or target: 'universal'
// https://github.com/webpack/webpack/issues/6522
// https://github.com/webpack/webpack/issues/6525
globalObject: `(typeof self !== 'undefined' ? self : this)`
}, rawConfig.output, {
filename: `${entryName}.js`,
chunkFilename: `${entryName}.[name].js`,
// use dynamic publicPath so this can be deployed anywhere
// the actual path will be determined at runtime by checking
// document.currentScript.src.
publicPath: ''
})
if (format === 'commonjs2') {
// #6188
delete rawConfig.output.library
}
return rawConfig
}
const configMap = {
commonjs: genConfig('commonjs2', 'common'),
umd: genConfig('umd', undefined, true),
'umd-min': genConfig('umd', 'umd.min')
}
const formatArray = (formats + '').split(',')
const configs = formatArray.map(format => configMap[format])
if (configs.indexOf(undefined) !== -1) {
const unknownFormats = formatArray.filter(f => configMap[f] === undefined).join(', ')
abort(
`Unknown library build formats: ${unknownFormats}`
)
}
return configs
}

View File

@ -0,0 +1,143 @@
const path = require('path')
const { resolveEntry, fileToComponentName } = require('./resolveWcEntry')
module.exports = (api, { target, entry, name, 'inline-vue': inlineVue }) => {
// Disable CSS extraction and turn on CSS shadow mode for vue-style-loader
process.env.VUE_CLI_CSS_SHADOW_MODE = true
const { log, error } = require('@vue/cli-shared-utils')
const abort = msg => {
log()
error(msg)
process.exit(1)
}
const cwd = api.getCwd()
const webpack = require('webpack')
const vueMajor = require('../../util/getVueMajor')(cwd)
if (vueMajor === 3) {
abort(`Vue 3 support of the web component target is still under development.`)
}
const isAsync = /async/.test(target)
// generate dynamic entry based on glob files
const resolvedFiles = require('globby').sync(entry.split(','), { cwd: api.resolve('.') })
if (!resolvedFiles.length) {
abort(`entry pattern "${entry}" did not match any files.`)
}
let libName
let prefix
if (resolvedFiles.length === 1) {
// in single mode, determine the lib name from filename
libName = name || fileToComponentName('', resolvedFiles[0]).kebabName
prefix = ''
if (libName.indexOf('-') < 0) {
abort(`--name must contain a hyphen when building a single web component.`)
}
} else {
// multi mode
libName = prefix = (name || api.service.pkg.name)
if (!libName) {
abort(`--name is required when building multiple web components.`)
}
}
const dynamicEntry = resolveEntry(prefix, libName, resolvedFiles, isAsync)
function genConfig (minify, genHTML) {
const config = api.resolveChainableWebpackConfig()
// make sure not to transpile wc-wrapper
config.module
.rule('js')
.exclude
.add(/vue-wc-wrapper/)
// only minify min entry
if (!minify) {
config.optimization.minimize(false)
}
config
.plugin('webpack-virtual-modules')
.use(require('webpack-virtual-modules'), [{
[dynamicEntry.filePath]: dynamicEntry.content
}])
config
.plugin('web-component-options')
.use(webpack.DefinePlugin, [{
'process.env.CUSTOM_ELEMENT_NAME': JSON.stringify(libName)
}])
// enable shadow mode in vue-loader
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
options.shadowMode = true
return options
})
if (genHTML) {
config
.plugin('demo-html')
.use(require('html-webpack-plugin'), [{
template: path.resolve(__dirname, `./demo-wc.html`),
inject: false,
filename: 'demo.html',
libName,
vueMajor,
components:
prefix === ''
? [libName]
: resolvedFiles.map(file => {
return fileToComponentName(prefix, file).kebabName
})
}])
}
// set entry/output last so it takes higher priority than user
// configureWebpack hooks
// set proxy entry for *.vue files
config.resolve
.alias
.set('~root', api.resolve('.'))
const rawConfig = api.resolveWebpackConfig(config)
// externalize Vue in case user imports it
rawConfig.externals = [
...(Array.isArray(rawConfig.externals) ? rawConfig.externals : [rawConfig.externals]),
{ ...(inlineVue || { vue: 'Vue' }) }
].filter(Boolean)
const entryName = `${libName}${minify ? `.min` : ``}`
rawConfig.entry = {
[entryName]: dynamicEntry.filePath
}
Object.assign(rawConfig.output, {
filename: `${entryName}.js`,
chunkFilename: `${libName}.[name]${minify ? `.min` : ``}.js`,
// use dynamic publicPath so this can be deployed anywhere
// the actual path will be determined at runtime by checking
// document.currentScript.src.
publicPath: ''
})
// to ensure that multiple copies of async wc bundles can co-exist
// on the same page.
rawConfig.output.uniqueName = `vue-lib-${libName}`
return rawConfig
}
return [
genConfig(false, true),
genConfig(true, false)
]
}

View File

@ -0,0 +1,67 @@
const path = require('path')
const camelizeRE = /-(\w)/g
const camelize = str => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
}
const hyphenateRE = /\B([A-Z])/g
const hyphenate = str => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
}
/**
* Creates the script to add the component to the custom elements
* @param {string} prefix The prefix for the component library
* @param {string} component The component name for single entry builds, component file for multi-entry builds
* @param {string} file The file for the component
* @param {boolean} isAsync Whether to load component async or not
*/
const createElement = (prefix, component, file, isAsync) => {
const { camelName, kebabName } = exports.fileToComponentName(prefix, component)
return isAsync
? `window.customElements.define('${kebabName}', wrap(Vue, () => import('~root/${file}?shadow')))\n`
: `import ${camelName} from '~root/${file}?shadow'\n` +
`window.customElements.define('${kebabName}', wrap(Vue, ${camelName}))\n`
}
exports.fileToComponentName = (prefix, file) => {
const basename = path.basename(file).replace(/\.(jsx?|vue)$/, '')
const camelName = camelize(basename)
const kebabName = `${prefix ? `${prefix}-` : ``}${hyphenate(basename)}`
return {
basename,
camelName,
kebabName
}
}
exports.resolveEntry = (prefix, libName, files, isAsync) => {
const filePath = path.resolve(__dirname, 'entry-wc.js')
const elements =
prefix === ''
? [createElement('', libName, files[0])]
: files.map(file => createElement(prefix, file, file, isAsync)).join('\n')
function resolveImportPath (mod) {
return require.resolve(mod).replace(/\\/g, '\\\\')
}
const content = `
import './setPublicPath'
import Vue from 'vue'
import wrap from '@vue/web-component-wrapper'
// runtime shared by every component chunk
import '${resolveImportPath('css-loader/dist/runtime/api.js')}'
import '${resolveImportPath('vue-style-loader/lib/addStylesShadow')}'
import '${resolveImportPath('@vue/vue-loader-v15/lib/runtime/componentNormalizer')}'
${elements}`.trim()
return {
filePath: filePath,
content: content
}
}

View File

@ -0,0 +1,23 @@
/* eslint-disable no-var */
// This file is imported into lib/wc client bundles.
if (typeof window !== 'undefined') {
var currentScript = window.document.currentScript
if (process.env.NEED_CURRENTSCRIPT_POLYFILL) {
var getCurrentScript = require('@soda/get-current-script')
currentScript = getCurrentScript()
// for backward compatibility, because previously we directly included the polyfill
if (!('currentScript' in document)) {
Object.defineProperty(document, 'currentScript', { get: getCurrentScript })
}
}
var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/)
if (src) {
__webpack_public_path__ = src[1] // eslint-disable-line
}
}
// Indicate to webpack that this file can be concatenated
export default null

View File

@ -0,0 +1,62 @@
const { chalk } = require('@vue/cli-shared-utils')
const getPadLength = require('../util/getPadLength')
module.exports = (api, options) => {
api.registerCommand('help', args => {
const commandName = args._[0]
if (!commandName) {
logMainHelp()
} else {
logHelpForCommand(commandName, api.service.commands[commandName])
}
})
function logMainHelp () {
console.log(
`\n Usage: vue-cli-service <command> [options]\n` +
`\n Commands:\n`
)
const commands = api.service.commands
const padLength = getPadLength(commands)
for (const name in commands) {
if (name !== 'help') {
const opts = commands[name].opts || {}
console.log(` ${
chalk.blue(name.padEnd(padLength))
}${
opts.description || ''
}`)
}
}
console.log(`\n run ${
chalk.green(`vue-cli-service help [command]`)
} for usage of a specific command.\n`)
}
function logHelpForCommand (name, command) {
if (!command) {
console.log(chalk.red(`\n command "${name}" does not exist.`))
} else {
const opts = command.opts || {}
if (opts.usage) {
console.log(`\n Usage: ${opts.usage}`)
}
if (opts.options) {
console.log(`\n Options:\n`)
const padLength = getPadLength(opts.options)
for (const [flags, description] of Object.entries(opts.options)) {
console.log(` ${
chalk.blue(flags.padEnd(padLength))
}${
description
}`)
}
}
if (opts.details) {
console.log()
console.log(opts.details.split('\n').map(line => ` ${line}`).join('\n'))
}
console.log()
}
}
}

View File

@ -0,0 +1,74 @@
module.exports = (api, options) => {
api.registerCommand(
'inspect',
{
description: 'inspect internal webpack config',
usage: 'vue-cli-service inspect [options] [...paths]',
options: {
'--mode': 'specify env mode (default: development)',
'--rule <ruleName>': 'inspect a specific module rule',
'--plugin <pluginName>': 'inspect a specific plugin',
'--rules': 'list all module rule names',
'--plugins': 'list all plugin names',
'--verbose': 'show full function definitions in output',
'--skip-plugins': 'comma-separated list of plugin names to skip for this run'
}
},
args => {
const { chalk, get } = require('@vue/cli-shared-utils')
const { toString } = require('webpack-chain')
const { highlight } = require('cli-highlight')
const config = api.resolveWebpackConfig()
const { _: paths, verbose } = args
let res
let hasUnnamedRule
if (args.rule) {
res = config.module.rules.find(r => r.__ruleNames[0] === args.rule)
} else if (args.plugin) {
res = config.plugins.find(p => p.__pluginName === args.plugin)
} else if (args.rules) {
res = config.module.rules.map(r => {
const name = r.__ruleNames ? r.__ruleNames[0] : 'Nameless Rule (*)'
hasUnnamedRule = hasUnnamedRule || !r.__ruleNames
return name
})
} else if (args.plugins) {
res = config.plugins.map(p => p.__pluginName || p.constructor.name)
} else if (paths.length > 1) {
res = {}
paths.forEach(path => {
res[path] = get(config, path)
})
} else if (paths.length === 1) {
res = get(config, paths[0])
} else {
res = config
}
const output = toString(res, { verbose })
console.log(highlight(output, { language: 'js' }))
// Log explanation for Nameless Rules
if (hasUnnamedRule) {
console.log(`--- ${chalk.green('Footnotes')} ---`)
console.log(`*: ${chalk.green(
'Nameless Rules'
)} were added through the ${chalk.green(
'configureWebpack()'
)} API (possibly by a plugin) instead of ${chalk.green(
'chainWebpack()'
)} (recommended).
You can run ${chalk.green(
'vue-cli-service inspect'
)} without any arguments to inspect the full config and read these rules' config.`)
}
}
)
}
module.exports.defaultModes = {
inspect: 'development'
}

View File

@ -0,0 +1,397 @@
const {
info,
error,
hasProjectYarn,
hasProjectPnpm,
IpcMessenger
} = require('@vue/cli-shared-utils')
const getBaseUrl = require('../util/getBaseUrl')
const defaults = {
host: '0.0.0.0',
port: 8080,
https: false
}
/** @type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
const baseUrl = getBaseUrl(options)
api.registerCommand('serve', {
description: 'start development server',
usage: 'vue-cli-service serve [options] [entry]',
options: {
'--open': `open browser on server start`,
'--copy': `copy url to clipboard on server start`,
'--stdin': `close when stdin ends`,
'--mode': `specify env mode (default: development)`,
'--host': `specify host (default: ${defaults.host})`,
'--port': `specify port (default: ${defaults.port})`,
'--https': `use https (default: ${defaults.https})`,
'--public': `specify the public network URL for the HMR client`,
'--skip-plugins': `comma-separated list of plugin names to skip for this run`
}
}, async function serve (args) {
info('Starting development server...')
// although this is primarily a dev server, it is possible that we
// are running it in a mode with a production env, e.g. in E2E tests.
const isInContainer = checkInContainer()
const isProduction = process.env.NODE_ENV === 'production'
const { chalk } = require('@vue/cli-shared-utils')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const portfinder = require('portfinder')
const prepareURLs = require('../util/prepareURLs')
const prepareProxy = require('../util/prepareProxy')
const launchEditorMiddleware = require('launch-editor-middleware')
const validateWebpackConfig = require('../util/validateWebpackConfig')
const isAbsoluteUrl = require('../util/isAbsoluteUrl')
// configs that only matters for dev server
api.chainWebpack(webpackConfig => {
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
if (!webpackConfig.get('devtool')) {
webpackConfig
.devtool('eval-cheap-module-source-map')
}
// https://github.com/webpack/webpack/issues/6642
// https://github.com/vuejs/vue-cli/issues/3539
webpackConfig
.output
.globalObject(`(typeof self !== 'undefined' ? self : this)`)
if (
!process.env.VUE_CLI_TEST &&
(!options.devServer.client ||
options.devServer.client.progress !== false)
) {
// the default progress plugin won't show progress due to infrastructreLogging.level
webpackConfig
.plugin('progress')
.use(require('progress-webpack-plugin'))
}
}
})
// resolve webpack config
const webpackConfig = api.resolveWebpackConfig()
// check for common config errors
validateWebpackConfig(webpackConfig, api, options)
// load user devServer options with higher priority than devServer
// in webpack config
const projectDevServerOptions = Object.assign(
webpackConfig.devServer || {},
options.devServer
)
// expose advanced stats
if (args.dashboard) {
const DashboardPlugin = require('../webpack/DashboardPlugin')
webpackConfig.plugins.push(new DashboardPlugin({
type: 'serve'
}))
}
// entry arg
const entry = args._[0]
if (entry) {
webpackConfig.entry = {
app: api.resolve(entry)
}
}
// resolve server options
const modesUseHttps = ['https', 'http2']
const serversUseHttps = ['https', 'spdy']
const optionsUseHttps = modesUseHttps.some(modeName => !!projectDevServerOptions[modeName]) ||
(typeof projectDevServerOptions.server === 'string' && serversUseHttps.includes(projectDevServerOptions.server)) ||
(typeof projectDevServerOptions.server === 'object' && projectDevServerOptions.server !== null && serversUseHttps.includes(projectDevServerOptions.server.type))
const useHttps = args.https || optionsUseHttps || defaults.https
const protocol = useHttps ? 'https' : 'http'
const host = args.host || process.env.HOST || projectDevServerOptions.host || defaults.host
portfinder.basePort = args.port || process.env.PORT || projectDevServerOptions.port || defaults.port
const port = await portfinder.getPortPromise()
const rawPublicUrl = args.public || projectDevServerOptions.public
const publicUrl = rawPublicUrl
? /^[a-zA-Z]+:\/\//.test(rawPublicUrl)
? rawPublicUrl
: `${protocol}://${rawPublicUrl}`
: null
const publicHost = publicUrl ? /^[a-zA-Z]+:\/\/([^/?#]+)/.exec(publicUrl)[1] : undefined
const urls = prepareURLs(
protocol,
host,
port,
isAbsoluteUrl(baseUrl) ? '/' : baseUrl
)
const localUrlForBrowser = publicUrl || urls.localUrlForBrowser
const proxySettings = prepareProxy(
projectDevServerOptions.proxy,
api.resolve('public')
)
// inject dev & hot-reload middleware entries
let webSocketURL
if (!isProduction) {
if (publicHost) {
// explicitly configured via devServer.public
webSocketURL = {
protocol: protocol === 'https' ? 'wss' : 'ws',
hostname: publicHost,
port
}
} else if (isInContainer) {
// can't infer public network url if inside a container
// infer it from the browser instead
webSocketURL = 'auto://0.0.0.0:0/ws'
} else {
// otherwise infer the url from the config
webSocketURL = {
protocol: protocol === 'https' ? 'wss' : 'ws',
hostname: urls.lanUrlForConfig || 'localhost',
port
}
}
if (process.env.APPVEYOR) {
webpackConfig.plugins.push(
new webpack.EntryPlugin(__dirname, 'webpack/hot/poll?500', { name: undefined })
)
}
}
const { projectTargets } = require('../util/targets')
const supportsIE = !!projectTargets
if (supportsIE) {
webpackConfig.plugins.push(
// must use undefined as name,
// to avoid dev server establishing an extra ws connection for the new entry
new webpack.EntryPlugin(__dirname, 'whatwg-fetch', { name: undefined })
)
}
// fixme: temporary fix to suppress dev server logging
// should be more robust to show necessary info but not duplicate errors
webpackConfig.infrastructureLogging = { ...webpackConfig.infrastructureLogging, level: 'none' }
webpackConfig.stats = 'errors-only'
// create compiler
const compiler = webpack(webpackConfig)
// handle compiler error
compiler.hooks.failed.tap('vue-cli-service serve', msg => {
error(msg)
process.exit(1)
})
// create server
const server = new WebpackDevServer(Object.assign({
historyApiFallback: {
disableDotRule: true,
htmlAcceptHeaders: [
'text/html',
'application/xhtml+xml'
],
rewrites: genHistoryApiFallbackRewrites(baseUrl, options.pages)
},
hot: !isProduction
}, projectDevServerOptions, {
host,
port,
server: {
type: protocol,
...(typeof projectDevServerOptions.server === 'object'
? projectDevServerOptions.server
: {})
},
proxy: proxySettings,
static: {
directory: api.resolve('public'),
publicPath: options.publicPath,
watch: !isProduction,
...projectDevServerOptions.static
},
client: {
webSocketURL,
logging: 'none',
overlay: isProduction // TODO disable this
? false
: { warnings: false, errors: true },
progress: !process.env.VUE_CLI_TEST,
...projectDevServerOptions.client
},
open: args.open || projectDevServerOptions.open,
setupExitSignals: true,
setupMiddlewares (middlewares, devServer) {
// launch editor support.
// this works with vue-devtools & @vue/cli-overlay
devServer.app.use('/__open-in-editor', launchEditorMiddleware(() => console.log(
`To specify an editor, specify the EDITOR env variable or ` +
`add "editor" field to your Vue project config.\n`
)))
// allow other plugins to register middlewares, e.g. PWA
// todo: migrate to the new API interface
api.service.devServerConfigFns.forEach(fn => fn(devServer.app, devServer))
if (projectDevServerOptions.setupMiddlewares) {
return projectDevServerOptions.setupMiddlewares(middlewares, devServer)
}
return middlewares
}
}), compiler)
if (args.stdin) {
process.stdin.on('end', () => {
server.stopCallback(() => {
process.exit(0)
})
})
process.stdin.resume()
}
// on appveyor, killing the process with SIGTERM causes execa to
// throw error
if (process.env.VUE_CLI_TEST) {
process.stdin.on('data', data => {
if (data.toString() === 'close') {
console.log('got close signal!')
server.stopCallback(() => {
process.exit(0)
})
}
})
}
return new Promise((resolve, reject) => {
// log instructions & open browser on first compilation complete
let isFirstCompile = true
compiler.hooks.done.tap('vue-cli-service serve', stats => {
if (stats.hasErrors()) {
return
}
let copied = ''
if (isFirstCompile && args.copy) {
try {
require('clipboardy').writeSync(localUrlForBrowser)
copied = chalk.dim('(copied to clipboard)')
} catch (_) {
/* catch exception if copy to clipboard isn't supported (e.g. WSL), see issue #3476 */
}
}
const networkUrl = publicUrl
? publicUrl.replace(/([^/])$/, '$1/')
: urls.lanUrlForTerminal
console.log()
console.log(` App running at:`)
console.log(` - Local: ${chalk.cyan(urls.localUrlForTerminal)} ${copied}`)
if (!isInContainer) {
console.log(` - Network: ${chalk.cyan(networkUrl)}`)
} else {
console.log()
console.log(chalk.yellow(` It seems you are running Vue CLI inside a container.`))
if (!publicUrl && options.publicPath && options.publicPath !== '/') {
console.log()
console.log(chalk.yellow(` Since you are using a non-root publicPath, the hot-reload socket`))
console.log(chalk.yellow(` will not be able to infer the correct URL to connect. You should`))
console.log(chalk.yellow(` explicitly specify the URL via ${chalk.blue(`devServer.public`)}.`))
console.log()
}
console.log(chalk.yellow(` Access the dev server via ${chalk.cyan(
`${protocol}://localhost:<your container's external mapped port>${options.publicPath}`
)}`))
}
console.log()
if (isFirstCompile) {
isFirstCompile = false
if (!isProduction) {
const buildCommand = hasProjectYarn(api.getCwd()) ? `yarn build` : hasProjectPnpm(api.getCwd()) ? `pnpm run build` : `npm run build`
console.log(` Note that the development build is not optimized.`)
console.log(` To create a production build, run ${chalk.cyan(buildCommand)}.`)
} else {
console.log(` App is served in production mode.`)
console.log(` Note this is for preview or E2E testing only.`)
}
console.log()
// Send final app URL
if (args.dashboard) {
const ipc = new IpcMessenger()
ipc.send({
vueServe: {
url: localUrlForBrowser
}
})
}
// resolve returned Promise
// so other commands can do api.service.run('serve').then(...)
resolve({
server,
url: localUrlForBrowser
})
} else if (process.env.VUE_CLI_TEST) {
// signal for test to check HMR
console.log('App updated')
}
})
server.start().catch(err => reject(err))
})
})
}
// https://stackoverflow.com/a/20012536
function checkInContainer () {
if ('CODESANDBOX_SSE' in process.env) {
return true
}
const fs = require('fs')
if (fs.existsSync(`/proc/1/cgroup`)) {
const content = fs.readFileSync(`/proc/1/cgroup`, 'utf-8')
return /:\/(lxc|docker|kubepods(\.slice)?)\//.test(content)
}
}
function genHistoryApiFallbackRewrites (baseUrl, pages = {}) {
const path = require('path')
const multiPageRewrites = Object
.keys(pages)
// sort by length in reversed order to avoid overrides
// eg. 'page11' should appear in front of 'page1'
.sort((a, b) => b.length - a.length)
.map(name => ({
from: new RegExp(`^/${name}`),
to: path.posix.join(baseUrl, pages[name].filename || `${name}.html`)
}))
return [
...multiPageRewrites,
{ from: /./, to: path.posix.join(baseUrl, 'index.html') }
]
}
module.exports.defaultModes = {
serve: 'development'
}

269
app_vue/node_modules/@vue/cli-service/lib/config/app.js generated vendored Normal file
View File

@ -0,0 +1,269 @@
// config that are specific to --target app
const fs = require('fs')
const path = require('path')
// ensure the filename passed to html-webpack-plugin is a relative path
// because it cannot correctly handle absolute paths
function ensureRelative (outputDir, _path) {
if (path.isAbsolute(_path)) {
return path.relative(outputDir, _path)
} else {
return _path
}
}
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
// only apply when there's no alternative target
if (process.env.VUE_CLI_BUILD_TARGET && process.env.VUE_CLI_BUILD_TARGET !== 'app') {
return
}
const isProd = process.env.NODE_ENV === 'production'
const isLegacyBundle = process.env.VUE_CLI_MODERN_MODE && !process.env.VUE_CLI_MODERN_BUILD
const outputDir = api.resolve(options.outputDir)
const getAssetPath = require('../util/getAssetPath')
const outputFilename = getAssetPath(
options,
`js/[name]${isLegacyBundle ? `-legacy` : ``}${isProd && options.filenameHashing ? '.[contenthash:8]' : ''}.js`
)
webpackConfig
.output
.filename(outputFilename)
.chunkFilename(outputFilename)
// FIXME: a temporary workaround to get accurate contenthash in `applyLegacy`
// Should use a better fix per discussions at <https://github.com/jantimon/html-webpack-plugin/issues/1554#issuecomment-753653580>
webpackConfig.optimization
.set('realContentHash', false)
// code splitting
if (process.env.NODE_ENV !== 'test') {
webpackConfig.optimization.splitChunks({
cacheGroups: {
defaultVendors: {
name: `chunk-vendors`,
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
}
// HTML plugin
const resolveClientEnv = require('../util/resolveClientEnv')
const htmlOptions = {
title: api.service.pkg.name,
scriptLoading: 'defer',
templateParameters: (compilation, assets, assetTags, pluginOptions) => {
// enhance html-webpack-plugin's built in template params
return Object.assign({
compilation: compilation,
webpackConfig: compilation.options,
htmlWebpackPlugin: {
tags: assetTags,
files: assets,
options: pluginOptions
}
}, resolveClientEnv(options, true /* raw */))
}
}
// handle indexPath
if (options.indexPath !== 'index.html') {
// why not set filename for html-webpack-plugin?
// 1. It cannot handle absolute paths
// 2. Relative paths causes incorrect SW manifest to be generated (#2007)
webpackConfig
.plugin('move-index')
.use(require('../webpack/MovePlugin'), [
path.resolve(outputDir, 'index.html'),
path.resolve(outputDir, options.indexPath)
])
}
// resolve HTML file(s)
const HTMLPlugin = require('html-webpack-plugin')
// const PreloadPlugin = require('@vue/preload-webpack-plugin')
const multiPageConfig = options.pages
const htmlPath = api.resolve('public/index.html')
const defaultHtmlPath = path.resolve(__dirname, 'index-default.html')
const publicCopyIgnore = ['**/.DS_Store']
if (!multiPageConfig) {
// default, single page setup.
htmlOptions.template = fs.existsSync(htmlPath)
? htmlPath
: defaultHtmlPath
publicCopyIgnore.push(api.resolve(htmlOptions.template).replace(/\\/g, '/'))
webpackConfig
.plugin('html')
.use(HTMLPlugin, [htmlOptions])
// FIXME: need to test out preload plugin's compatibility with html-webpack-plugin 4/5
// if (!isLegacyBundle) {
// // inject preload/prefetch to HTML
// webpackConfig
// .plugin('preload')
// .use(PreloadPlugin, [{
// rel: 'preload',
// include: 'initial',
// fileBlacklist: [/\.map$/, /hot-update\.js$/]
// }])
// webpackConfig
// .plugin('prefetch')
// .use(PreloadPlugin, [{
// rel: 'prefetch',
// include: 'asyncChunks'
// }])
// }
} else {
// multi-page setup
webpackConfig.entryPoints.clear()
const pages = Object.keys(multiPageConfig)
const normalizePageConfig = c => typeof c === 'string' ? { entry: c } : c
pages.forEach(name => {
const pageConfig = normalizePageConfig(multiPageConfig[name])
const {
entry,
template = `public/${name}.html`,
filename = `${name}.html`,
chunks = ['chunk-vendors', 'chunk-common', name]
} = pageConfig
// Currently Cypress v3.1.0 comes with a very old version of Node,
// which does not support object rest syntax.
// (https://github.com/cypress-io/cypress/issues/2253)
// So here we have to extract the customHtmlOptions manually.
const customHtmlOptions = {}
for (const key in pageConfig) {
if (
!['entry', 'template', 'filename', 'chunks'].includes(key)
) {
customHtmlOptions[key] = pageConfig[key]
}
}
// inject entry
const entries = Array.isArray(entry) ? entry : [entry]
webpackConfig.entry(name).merge(entries.map(e => api.resolve(e)))
// trim inline loader
// * See https://github.com/jantimon/html-webpack-plugin/blob/master/docs/template-option.md#2-setting-a-loader-directly-for-the-template
const templateWithoutLoader = template.replace(/^.+!/, '').replace(/\?.+$/, '')
// resolve page index template
const hasDedicatedTemplate = fs.existsSync(api.resolve(templateWithoutLoader))
const templatePath = hasDedicatedTemplate
? template
: fs.existsSync(htmlPath)
? htmlPath
: defaultHtmlPath
publicCopyIgnore.push(api.resolve(templateWithoutLoader).replace(/\\/g, '/'))
// inject html plugin for the page
const pageHtmlOptions = Object.assign(
{},
htmlOptions,
{
chunks,
template: templatePath,
filename: ensureRelative(outputDir, filename)
},
customHtmlOptions
)
webpackConfig
.plugin(`html-${name}`)
.use(HTMLPlugin, [pageHtmlOptions])
})
// FIXME: preload plugin is not compatible with webpack 5 / html-webpack-plugin 4 yet
// if (!isLegacyBundle) {
// pages.forEach(name => {
// const filename = ensureRelative(
// outputDir,
// normalizePageConfig(multiPageConfig[name]).filename || `${name}.html`
// )
// webpackConfig
// .plugin(`preload-${name}`)
// .use(PreloadPlugin, [{
// rel: 'preload',
// includeHtmlNames: [filename],
// include: {
// type: 'initial',
// entries: [name]
// },
// fileBlacklist: [/\.map$/, /hot-update\.js$/]
// }])
// webpackConfig
// .plugin(`prefetch-${name}`)
// .use(PreloadPlugin, [{
// rel: 'prefetch',
// includeHtmlNames: [filename],
// include: {
// type: 'asyncChunks',
// entries: [name]
// }
// }])
// })
// }
}
// CORS and Subresource Integrity
if (options.crossorigin != null || options.integrity) {
webpackConfig
.plugin('cors')
.use(require('../webpack/CorsPlugin'), [{
crossorigin: options.crossorigin,
integrity: options.integrity,
publicPath: options.publicPath
}])
}
// copy static assets in public/
const publicDir = api.resolve('public')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const PlaceholderPlugin = class PlaceholderPlugin { apply () {} }
const copyOptions = {
patterns: [{
from: publicDir,
to: outputDir,
toType: 'dir',
noErrorOnMissing: true,
globOptions: {
ignore: publicCopyIgnore
},
info: {
minimized: true
}
}]
}
if (fs.existsSync(publicDir)) {
if (isLegacyBundle) {
webpackConfig.plugin('copy').use(PlaceholderPlugin, [copyOptions])
} else {
webpackConfig.plugin('copy').use(CopyWebpackPlugin, [copyOptions])
}
}
})
}

View File

@ -0,0 +1,47 @@
/** @type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
const getAssetPath = require('../util/getAssetPath')
const genAssetSubPath = dir => {
return getAssetPath(
options,
`${dir}/[name]${options.filenameHashing ? '.[hash:8]' : ''}[ext]`
)
}
api.chainWebpack(webpackConfig => {
webpackConfig.module
.rule('svg')
.test(/\.(svg)(\?.*)?$/)
// do not base64-inline SVGs.
// https://github.com/facebookincubator/create-react-app/pull/1180
.set('type', 'asset/resource')
.set('generator', {
filename: genAssetSubPath('img')
})
webpackConfig.module
.rule('images')
.test(/\.(png|jpe?g|gif|webp|avif)(\?.*)?$/)
.set('type', 'asset')
.set('generator', {
filename: genAssetSubPath('img')
})
webpackConfig.module
.rule('media')
.test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/)
.set('type', 'asset')
.set('generator', {
filename: genAssetSubPath('media')
})
webpackConfig.module
.rule('fonts')
.test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i)
.set('type', 'asset')
.set('generator', {
filename: genAssetSubPath('fonts')
})
})
}

View File

@ -0,0 +1,226 @@
const path = require('path')
/** @type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
const cwd = api.getCwd()
const webpack = require('webpack')
const vueMajor = require('../util/getVueMajor')(cwd)
api.chainWebpack(webpackConfig => {
const isLegacyBundle = process.env.VUE_CLI_MODERN_MODE && !process.env.VUE_CLI_MODERN_BUILD
const resolveLocal = require('../util/resolveLocal')
// https://github.com/webpack/webpack/issues/14532#issuecomment-947525539
webpackConfig.output.set('hashFunction', 'xxhash64')
// https://github.com/webpack/webpack/issues/11467#issuecomment-691873586
webpackConfig.module
.rule('esm')
.test(/\.m?jsx?$/)
.resolve.set('fullySpecified', false)
webpackConfig
.mode('development')
.context(api.service.context)
.entry('app')
.add('./src/main.js')
.end()
.output
.path(api.resolve(options.outputDir))
.filename(isLegacyBundle ? '[name]-legacy.js' : '[name].js')
.publicPath(options.publicPath)
webpackConfig.resolve
.extensions
.merge(['.mjs', '.js', '.jsx', '.vue', '.json', '.wasm'])
.end()
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
.end()
.alias
.set('@', api.resolve('src'))
webpackConfig.resolveLoader
.modules
.add('node_modules')
.add(api.resolve('node_modules'))
.add(resolveLocal('node_modules'))
webpackConfig.module
.noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/)
// js is handled by cli-plugin-babel ---------------------------------------
// vue-loader --------------------------------------------------------------
let cacheLoaderPath
try {
cacheLoaderPath = require.resolve('cache-loader')
} catch (e) {}
if (vueMajor === 2) {
// for Vue 2 projects
const partialIdentifier = {
'vue-loader': require('@vue/vue-loader-v15/package.json').version,
'@vue/component-compiler-utils': require('@vue/component-compiler-utils/package.json').version
}
try {
partialIdentifier['vue-template-compiler'] = require('vue-template-compiler/package.json').version
} catch (e) {
// For Vue 2.7 projects, `vue-template-compiler` is not required
}
const vueLoaderCacheConfig = api.genCacheConfig('vue-loader', partialIdentifier)
webpackConfig.resolve
.alias
.set(
'vue$',
options.runtimeCompiler
? 'vue/dist/vue.esm.js'
: 'vue/dist/vue.runtime.esm.js'
)
if (cacheLoaderPath) {
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('cache-loader')
.loader(cacheLoaderPath)
.options(vueLoaderCacheConfig)
}
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('vue-loader')
.loader(require.resolve('@vue/vue-loader-v15'))
.options(Object.assign({
compilerOptions: {
whitespace: 'condense'
}
}, cacheLoaderPath ? vueLoaderCacheConfig : {}))
webpackConfig
.plugin('vue-loader')
.use(require('@vue/vue-loader-v15').VueLoaderPlugin)
// some plugins may implicitly relies on the `vue-loader` dependency path name
// such as vue-cli-plugin-apollo
// <https://github.com/Akryum/vue-cli-plugin-apollo/blob/d9fe48c61cc19db88fef4e4aa5e49b31aa0c44b7/index.js#L88>
// so we need a hotfix for that
webpackConfig
.resolveLoader
.modules
.prepend(path.resolve(__dirname, './vue-loader-v15-resolve-compat'))
} else if (vueMajor === 3) {
// for Vue 3 projects
const vueLoaderCacheConfig = api.genCacheConfig('vue-loader', {
'vue-loader': require('vue-loader/package.json').version
})
webpackConfig.resolve
.alias
.set(
'vue$',
options.runtimeCompiler
? 'vue/dist/vue.esm-bundler.js'
: 'vue/dist/vue.runtime.esm-bundler.js'
)
if (cacheLoaderPath) {
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('cache-loader')
.loader(cacheLoaderPath)
.options(vueLoaderCacheConfig)
}
webpackConfig.module
.rule('vue')
.test(/\.vue$/)
.use('vue-loader')
.loader(require.resolve('vue-loader'))
.options({
...vueLoaderCacheConfig,
babelParserPlugins: ['jsx', 'classProperties', 'decorators-legacy']
})
webpackConfig
.plugin('vue-loader')
.use(require('vue-loader').VueLoaderPlugin)
// feature flags <http://link.vuejs.org/feature-flags>
webpackConfig
.plugin('feature-flags')
.use(webpack.DefinePlugin, [{
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false'
}])
}
// https://github.com/vuejs/vue-loader/issues/1435#issuecomment-869074949
webpackConfig.module
.rule('vue-style')
.test(/\.vue$/)
.resourceQuery(/type=style/)
.sideEffects(true)
// Other common pre-processors ---------------------------------------------
const maybeResolve = name => {
try {
return require.resolve(name)
} catch (error) {
return name
}
}
webpackConfig.module
.rule('pug')
.test(/\.pug$/)
.oneOf('pug-vue')
.resourceQuery(/vue/)
.use('pug-plain-loader')
.loader(maybeResolve('pug-plain-loader'))
.end()
.end()
.oneOf('pug-template')
.use('raw')
.loader(maybeResolve('raw-loader'))
.end()
.use('pug-plain-loader')
.loader(maybeResolve('pug-plain-loader'))
.end()
.end()
const resolveClientEnv = require('../util/resolveClientEnv')
webpackConfig
.plugin('define')
.use(webpack.DefinePlugin, [
resolveClientEnv(options)
])
webpackConfig
.plugin('case-sensitive-paths')
.use(require('case-sensitive-paths-webpack-plugin'))
// friendly error plugin displays very confusing errors when webpack
// fails to resolve a loader, so we provide custom handlers to improve it
const { transformer, formatter } = require('../util/resolveLoaderError')
webpackConfig
.plugin('friendly-errors')
.use(require('@soda/friendly-errors-webpack-plugin'), [{
additionalTransformers: [transformer],
additionalFormatters: [formatter]
}])
const TerserPlugin = require('terser-webpack-plugin')
const terserOptions = require('./terserOptions')
webpackConfig.optimization
.minimizer('terser')
.use(TerserPlugin, [terserOptions(options)])
})
}

234
app_vue/node_modules/@vue/cli-service/lib/config/css.js generated vendored Normal file
View File

@ -0,0 +1,234 @@
const fs = require('fs')
const path = require('path')
const { chalk, semver, loadModule } = require('@vue/cli-shared-utils')
const isAbsoluteUrl = require('../util/isAbsoluteUrl')
const findExisting = (context, files) => {
for (const file of files) {
if (fs.existsSync(path.join(context, file))) {
return file
}
}
}
module.exports = (api, rootOptions) => {
api.chainWebpack(webpackConfig => {
const getAssetPath = require('../util/getAssetPath')
const shadowMode = !!process.env.VUE_CLI_CSS_SHADOW_MODE
const isProd = process.env.NODE_ENV === 'production'
const {
extract = isProd,
sourceMap = false,
loaderOptions = {}
} = rootOptions.css || {}
const shouldExtract = extract !== false && !shadowMode
const filename = getAssetPath(
rootOptions,
`css/[name]${rootOptions.filenameHashing ? '.[contenthash:8]' : ''}.css`
)
const extractOptions = Object.assign({
filename,
chunkFilename: filename
}, extract && typeof extract === 'object' ? extract : {})
// when project publicPath is a relative path
// use relative publicPath in extracted CSS based on extract location
const cssPublicPath = (isAbsoluteUrl(rootOptions.publicPath) || rootOptions.publicPath.startsWith('/'))
? rootOptions.publicPath
: process.env.VUE_CLI_BUILD_TARGET === 'lib'
// in lib mode, CSS is extracted to dist root.
? './'
: '../'.repeat(
extractOptions.filename
.replace(/^\.[/\\]/, '')
.split(/[/\\]/g)
.length - 1
)
// check if the project has a valid postcss config
// if it doesn't, don't use postcss-loader for direct style imports
// because otherwise it would throw error when attempting to load postcss config
const hasPostCSSConfig = !!(loaderOptions.postcss || api.service.pkg.postcss || findExisting(api.resolve('.'), [
'.postcssrc',
'.postcssrc.js',
'postcss.config.js',
'.postcssrc.yaml',
'.postcssrc.json'
]))
if (!hasPostCSSConfig) {
// #6342
// NPM 6 may incorrectly hoist postcss 7 to the same level of autoprefixer
// So we have to run a preflight check to tell the users how to fix it
const autoprefixerDirectory = path.dirname(require.resolve('autoprefixer/package.json'))
const postcssPkg = loadModule('postcss/package.json', autoprefixerDirectory)
const postcssVersion = postcssPkg.version
if (!semver.satisfies(postcssVersion, '8.x')) {
throw new Error(
`The package manager has hoisted a wrong version of ${chalk.cyan('postcss')}, ` +
`please run ${chalk.cyan('npm i postcss@8 -D')} to fix it.`
)
}
loaderOptions.postcss = {
postcssOptions: {
plugins: [
require('autoprefixer')
]
}
}
}
// if building for production but not extracting CSS, we need to minimize
// the embbeded inline CSS as they will not be going through the optimizing
// plugin.
const needInlineMinification = isProd && !shouldExtract
const cssnanoOptions = {
preset: ['default', {
mergeLonghand: false,
cssDeclarationSorter: false
}]
}
if (rootOptions.productionSourceMap && sourceMap) {
cssnanoOptions.map = { inline: false }
}
function createCSSRule (lang, test, loader, options) {
const baseRule = webpackConfig.module.rule(lang).test(test)
// rules for <style module>
const vueModulesRule = baseRule.oneOf('vue-modules').resourceQuery(/module/)
applyLoaders(vueModulesRule, true)
// rules for <style>
const vueNormalRule = baseRule.oneOf('vue').resourceQuery(/\?vue/)
applyLoaders(vueNormalRule)
// rules for *.module.* files
const extModulesRule = baseRule.oneOf('normal-modules').test(/\.module\.\w+$/)
applyLoaders(extModulesRule)
// rules for normal CSS imports
const normalRule = baseRule.oneOf('normal')
applyLoaders(normalRule)
function applyLoaders (rule, forceCssModule = false) {
if (shouldExtract) {
rule
.use('extract-css-loader')
.loader(require('mini-css-extract-plugin').loader)
.options({
publicPath: cssPublicPath
})
} else {
rule
.use('vue-style-loader')
.loader(require.resolve('vue-style-loader'))
.options({
sourceMap,
shadowMode
})
}
const cssLoaderOptions = Object.assign({
sourceMap,
importLoaders: (
1 + // stylePostLoader injected by vue-loader
1 + // postcss-loader
(needInlineMinification ? 1 : 0)
)
}, loaderOptions.css)
if (forceCssModule) {
cssLoaderOptions.modules = {
...cssLoaderOptions.modules,
auto: () => true
}
}
if (cssLoaderOptions.modules) {
cssLoaderOptions.modules = {
localIdentName: '[name]_[local]_[hash:base64:5]',
...cssLoaderOptions.modules
}
}
rule
.use('css-loader')
.loader(require.resolve('css-loader'))
.options(cssLoaderOptions)
if (needInlineMinification) {
rule
.use('cssnano')
.loader(require.resolve('postcss-loader'))
.options({
sourceMap,
postcssOptions: {
plugins: [require('cssnano')(cssnanoOptions)]
}
})
}
rule
.use('postcss-loader')
.loader(require.resolve('postcss-loader'))
.options(Object.assign({ sourceMap }, loaderOptions.postcss))
if (loader) {
let resolvedLoader
try {
resolvedLoader = require.resolve(loader)
} catch (error) {
resolvedLoader = loader
}
rule
.use(loader)
.loader(resolvedLoader)
.options(Object.assign({ sourceMap }, options))
}
}
}
createCSSRule('css', /\.css$/)
createCSSRule('postcss', /\.p(ost)?css$/)
createCSSRule('scss', /\.scss$/, 'sass-loader', Object.assign(
{},
loaderOptions.scss || loaderOptions.sass
))
createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign(
{},
loaderOptions.sass,
{
sassOptions: Object.assign(
{},
loaderOptions.sass && loaderOptions.sass.sassOptions,
{
indentedSyntax: true
}
)
}
))
createCSSRule('less', /\.less$/, 'less-loader', loaderOptions.less)
createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', loaderOptions.stylus)
// inject CSS extraction plugin
if (shouldExtract) {
webpackConfig
.plugin('extract-css')
.use(require('mini-css-extract-plugin'), [extractOptions])
// minify extracted CSS
webpackConfig.optimization
.minimizer('css')
.use(require('css-minimizer-webpack-plugin'), [{
parallel: rootOptions.parallel,
minimizerOptions: cssnanoOptions
}])
}
})
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Vue App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1,15 @@
/** @type {import('@vue/cli-service').ServicePlugin} */
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
if (process.env.NODE_ENV === 'production') {
webpackConfig
.mode('production')
.devtool(options.productionSourceMap ? 'source-map' : false)
// disable optimization during tests to speed things up
if (process.env.VUE_CLI_TEST && !process.env.VUE_CLI_TEST_MINIMIZE) {
webpackConfig.optimization.minimize(false)
}
}
})
}

View File

@ -0,0 +1,110 @@
// @ts-check
const TerserPlugin = require('terser-webpack-plugin')
const genTerserOptions = (defaultOptions, options) => {
const userOptions = options.terser && options.terser.terserOptions
// user's config is first
return {
...defaultOptions,
...userOptions
}
}
const terserMinify = (options) => ({
terserOptions: genTerserOptions(
{
compress: {
// turn off flags with small gains to speed up minification
arrows: false,
collapse_vars: false, // 0.3kb
comparisons: false,
computed_props: false,
hoist_funs: false,
hoist_props: false,
hoist_vars: false,
inline: false,
loops: false,
negate_iife: false,
properties: false,
reduce_funcs: false,
reduce_vars: false,
switches: false,
toplevel: false,
typeofs: false,
// a few flags with noticeable gains/speed ratio
// numbers based on out of the box vendor bundle
booleans: true, // 0.7kb
if_return: true, // 0.4kb
sequences: true, // 0.7kb
unused: true, // 2.3kb
// required features to drop conditional branches
conditionals: true,
dead_code: true,
evaluate: true
},
mangle: {
safari10: true
}
},
options
),
parallel: options.parallel,
extractComments: false
})
// `terserOptions` options will be passed to `esbuild`
// Link to options - https://esbuild.github.io/api/#minify
const esbuildMinify = (options) => ({
minify: TerserPlugin.esbuildMinify,
terserOptions: genTerserOptions(
{
minify: false,
minifyWhitespace: true,
minifyIdentifiers: false,
minifySyntax: true
},
options
),
parallel: options.parallel
})
// `terserOptions` options will be passed to `swc` (`@swc/core`)
// Link to options - https://swc.rs/docs/config-js-minify
const swcMinify = (options) => ({
minify: TerserPlugin.swcMinify,
terserOptions: genTerserOptions(
{
compress: {
unused: true
},
mangle: true
},
options
),
parallel: options.parallel
})
// `terserOptions` options will be passed to `uglify-js`
// Link to options - https://github.com/mishoo/UglifyJS#minify-options
const uglifyJsMinify = (options) => ({
minify: TerserPlugin.uglifyJsMinify,
terserOptions: genTerserOptions({}, options),
parallel: options.parallel
})
// Currently we do not allow custom minify function
const getMinify = (options) => {
const { minify = 'terser' } = options.terser || {}
const minifyMap = {
terser: terserMinify,
esbuild: esbuildMinify,
swc: swcMinify,
uglifyJs: uglifyJsMinify
}
return minifyMap[minify](options)
}
module.exports = getMinify

View File

@ -0,0 +1 @@
module.exports = require('@vue/vue-loader-v15')

147
app_vue/node_modules/@vue/cli-service/lib/options.js generated vendored Normal file
View File

@ -0,0 +1,147 @@
const { createSchema, validate } = require('@vue/cli-shared-utils')
const schema = createSchema(joi => joi.object({
publicPath: joi.string().allow(''),
outputDir: joi.string(),
assetsDir: joi.string().allow(''),
indexPath: joi.string(),
filenameHashing: joi.boolean(),
runtimeCompiler: joi.boolean(),
transpileDependencies: joi.alternatives().try(
joi.boolean(),
joi.array()
),
productionSourceMap: joi.boolean(),
parallel: joi.alternatives().try(
joi.boolean(),
joi.number().integer()
),
devServer: joi.object(),
pages: joi.object().pattern(
/\w+/,
joi.alternatives().try(
joi.string().required(),
joi.array().items(joi.string().required()),
joi.object().keys({
entry: joi.alternatives().try(
joi.string().required(),
joi.array().items(joi.string().required())
).required()
}).unknown(true)
)
),
crossorigin: joi.string().valid('', 'anonymous', 'use-credentials'),
integrity: joi.boolean(),
// css
css: joi.object({
extract: joi.alternatives().try(joi.boolean(), joi.object()),
sourceMap: joi.boolean(),
loaderOptions: joi.object({
css: joi.object(),
sass: joi.object(),
scss: joi.object(),
less: joi.object(),
stylus: joi.object(),
postcss: joi.object()
})
}),
// webpack
chainWebpack: joi.func(),
configureWebpack: joi.alternatives().try(
joi.object(),
joi.func()
),
// known runtime options for built-in plugins
lintOnSave: joi.any().valid(true, false, 'error', 'warning', 'default'),
pwa: joi.object(),
// terser
terser: joi.object({
minify: joi.string().valid('terser', 'esbuild', 'swc', 'uglifyJs'),
terserOptions: joi.object()
}),
// 3rd party plugin options
pluginOptions: joi.object()
}))
exports.validate = (options, cb) => {
validate(options, schema, cb)
}
// #2110
// https://github.com/nodejs/node/issues/19022
// in some cases cpus() returns undefined, and may simply throw in the future
function hasMultipleCores () {
try {
return require('os').cpus().length > 1
} catch (e) {
return false
}
}
exports.defaults = () => ({
// project deployment base
publicPath: '/',
// where to output built files
outputDir: 'dist',
// where to put static assets (js/css/img/font/...)
assetsDir: '',
// filename for index.html (relative to outputDir)
indexPath: 'index.html',
// whether filename will contain hash part
filenameHashing: true,
// boolean, use full build?
runtimeCompiler: false,
// whether to transpile all dependencies
transpileDependencies: false,
// sourceMap for production build?
productionSourceMap: !process.env.VUE_CLI_TEST,
// use thread-loader for babel & TS in production build
// enabled by default if the machine has more than 1 cores
parallel: hasMultipleCores(),
// multi-page config
pages: undefined,
// <script type="module" crossorigin="use-credentials">
// #1656, #1867, #2025
crossorigin: undefined,
// subresource integrity
integrity: false,
css: {
// extract: true,
// modules: false,
// sourceMap: false,
// loaderOptions: {}
},
// whether to use eslint-loader
lintOnSave: 'default',
devServer: {
/*
open: process.platform === 'darwin',
host: '0.0.0.0',
port: 8080,
https: false,
hotOnly: false,
proxy: null, // string | Object
before: app => {}
*/
}
})

View File

@ -0,0 +1,7 @@
const path = require('path')
module.exports = function getAssetPath (options, filePath) {
return options.assetsDir
? path.posix.join(options.assetsDir, filePath)
: filePath
}

View File

@ -0,0 +1,3 @@
module.exports = function getBaseUrl (options) {
return options.publicPath === 'auto' ? '' : options.publicPath
}

View File

@ -0,0 +1,9 @@
module.exports = function getPadLength (obj) {
let longest = 10
for (const name in obj) {
if (name.length + 1 > longest) {
longest = name.length + 1
}
}
return longest
}

View File

@ -0,0 +1,13 @@
const { semver, loadModule } = require('@vue/cli-shared-utils')
/**
* Get the major Vue version that the user project uses
* @param {string} cwd the user project root
* @returns {2|3}
*/
module.exports = function getVueMajor (cwd) {
const vue = loadModule('vue', cwd)
// TODO: make Vue 3 the default version
const vueMajor = vue ? semver.major(vue.version) : 2
return vueMajor
}

View File

@ -0,0 +1,4 @@
module.exports = function isAbsoluteUrl (url) {
// A URL is considered absolute if it begins with "<scheme>://" or "//"
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
}

View File

@ -0,0 +1,38 @@
const fs = require('fs')
const path = require('path')
const { pathToFileURL } = require('url')
const isFileEsm = require('is-file-esm')
const { loadModule } = require('@vue/cli-shared-utils')
module.exports = function loadFileConfig (context) {
let fileConfig, fileConfigPath
const possibleConfigPaths = [
process.env.VUE_CLI_SERVICE_CONFIG_PATH,
'./vue.config.js',
'./vue.config.cjs',
'./vue.config.mjs'
]
for (const p of possibleConfigPaths) {
const resolvedPath = p && path.resolve(context, p)
if (resolvedPath && fs.existsSync(resolvedPath)) {
fileConfigPath = resolvedPath
break
}
}
if (fileConfigPath) {
const { esm } = isFileEsm.sync(fileConfigPath)
if (esm) {
fileConfig = import(pathToFileURL(fileConfigPath))
} else {
fileConfig = loadModule(fileConfigPath, context)
}
}
return {
fileConfig,
fileConfigPath
}
}

View File

@ -0,0 +1,205 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file at
* https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
*/
const fs = require('fs')
const url = require('url')
const path = require('path')
const { chalk } = require('@vue/cli-shared-utils')
const address = require('address')
const defaultConfig = {
logLevel: 'silent',
secure: false,
changeOrigin: true,
ws: true,
xfwd: true
}
module.exports = function prepareProxy (proxy, appPublicFolder) {
// `proxy` lets you specify alternate servers for specific requests.
// It can either be a string or an object conforming to the Webpack dev server proxy configuration
// https://webpack.github.io/docs/webpack-dev-server.html
if (!proxy) {
return undefined
}
if (Array.isArray(proxy) || (typeof proxy !== 'object' && typeof proxy !== 'string')) {
console.log(
chalk.red(
'When specified, "proxy" in package.json must be a string or an object.'
)
)
console.log(
chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".')
)
console.log(
chalk.red(
'Either remove "proxy" from package.json, or make it an object.'
)
)
process.exit(1)
}
// If proxy is specified, let it handle any request except for
// files in the public folder and requests to the WebpackDevServer socket endpoint.
// https://github.com/facebook/create-react-app/issues/6720
function mayProxy (pathname) {
const maybePublicPath = path.resolve(appPublicFolder, pathname.slice(1))
const isPublicFileRequest = fs.existsSync(maybePublicPath) && fs.statSync(maybePublicPath).isFile()
const isWdsEndpointRequest = pathname.startsWith('/sockjs-node') // used by webpackHotDevClient
return !(isPublicFileRequest || isWdsEndpointRequest)
}
function createProxyEntry (target, usersOnProxyReq, context) {
// #2478
// There're a little-known use case that the `target` field is an object rather than a string
// https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/https.md
if (typeof target === 'string' && process.platform === 'win32') {
target = resolveLoopback(target)
}
return {
target,
context (pathname, req) {
// is a static asset
if (!mayProxy(pathname)) {
return false
}
if (context) {
// Explicit context, e.g. /api
return pathname.match(context)
} else {
// not a static request
if (req.method !== 'GET') {
return true
}
// Heuristics: if request `accept`s text/html, we pick /index.html.
// Modern browsers include text/html into `accept` header when navigating.
// However API calls like `fetch()` wont generally accept text/html.
// If this heuristic doesnt work well for you, use a custom `proxy` object.
return (
req.headers.accept &&
req.headers.accept.indexOf('text/html') === -1
)
}
},
onProxyReq (proxyReq, req, res) {
if (usersOnProxyReq) {
usersOnProxyReq(proxyReq, req, res)
}
// Browsers may send Origin headers even with same-origin
// requests. To prevent CORS issues, we have to change
// the Origin to match the target URL.
if (!proxyReq.agent && proxyReq.getHeader('origin')) {
proxyReq.setHeader('origin', target)
}
},
onError: onProxyError(target)
}
}
// Support proxy as a string for those who are using the simple proxy option
if (typeof proxy === 'string') {
if (!/^http(s)?:\/\//.test(proxy)) {
console.log(
chalk.red(
'When "proxy" is specified in package.json it must start with either http:// or https://'
)
)
process.exit(1)
}
return [
Object.assign({}, defaultConfig, createProxyEntry(proxy))
]
}
// Otherwise, proxy is an object so create an array of proxies to pass to webpackDevServer
return Object.keys(proxy).map(context => {
const config = proxy[context]
if (!Object.prototype.hasOwnProperty.call(config, 'target')) {
console.log(
chalk.red(
'When `proxy` in package.json is an object, each `context` object must have a ' +
'`target` property specified as a url string'
)
)
process.exit(1)
}
const entry = createProxyEntry(config.target, config.onProxyReq, context)
return Object.assign({}, defaultConfig, config, entry)
})
}
function resolveLoopback (proxy) {
const o = new url.URL(proxy)
o.host = undefined
if (o.hostname !== 'localhost') {
return proxy
}
// Unfortunately, many languages (unlike node) do not yet support IPv6.
// This means even though localhost resolves to ::1, the application
// must fall back to IPv4 (on 127.0.0.1).
// We can re-enable this in a few years.
/* try {
o.hostname = address.ipv6() ? '::1' : '127.0.0.1';
} catch (_ignored) {
o.hostname = '127.0.0.1';
} */
try {
// Check if we're on a network; if we are, chances are we can resolve
// localhost. Otherwise, we can just be safe and assume localhost is
// IPv4 for maximum compatibility.
if (!address.ip()) {
o.hostname = '127.0.0.1'
}
} catch (_ignored) {
o.hostname = '127.0.0.1'
}
return url.format(o)
}
// We need to provide a custom onError function for httpProxyMiddleware.
// It allows us to log custom error messages on the console.
function onProxyError (proxy) {
return (err, req, res) => {
const host = req.headers && req.headers.host
console.log(
chalk.red('Proxy error:') +
' Could not proxy request ' +
chalk.cyan(req.url) +
' from ' +
chalk.cyan(host) +
' to ' +
chalk.cyan(proxy) +
'.'
)
console.log(
'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
chalk.cyan(err.code) +
').'
)
console.log()
// And immediately send the proper error response to the client.
// Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
if (res.writeHead && !res.headersSent) {
res.writeHead(500)
}
res.end(
'Proxy error: Could not proxy request ' +
req.url +
' from ' +
host +
' to ' +
proxy +
' (' +
err.code +
').'
)
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file at
* https://github.com/facebookincubator/create-react-app/blob/master/LICENSE
*/
const url = require('url')
const { chalk } = require('@vue/cli-shared-utils')
const address = require('address')
const defaultGateway = require('default-gateway')
module.exports = function prepareUrls (protocol, host, port, pathname = '/') {
const formatUrl = hostname =>
url.format({
protocol,
hostname,
port,
pathname
})
const prettyPrintUrl = hostname =>
url.format({
protocol,
hostname,
port: chalk.bold(port),
pathname
})
const isUnspecifiedHost = host === '0.0.0.0' || host === '::'
let prettyHost, lanUrlForConfig
let lanUrlForTerminal = chalk.gray('unavailable')
if (isUnspecifiedHost) {
prettyHost = 'localhost'
try {
// This can only return an IPv4 address
const result = defaultGateway.v4.sync()
lanUrlForConfig = address.ip(result && result.interface)
if (lanUrlForConfig) {
// Check if the address is a private ip
// https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
if (
/^10[.]|^172[.](1[6-9]|2[0-9]|3[0-1])[.]|^192[.]168[.]/.test(
lanUrlForConfig
)
) {
// Address is private, format it for later use
lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig)
} else {
// Address is not private, so we will discard it
lanUrlForConfig = undefined
}
}
} catch (_e) {
// ignored
}
} else {
prettyHost = host
lanUrlForConfig = host
lanUrlForTerminal = prettyPrintUrl(lanUrlForConfig)
}
const localUrlForTerminal = prettyPrintUrl(prettyHost)
const localUrlForBrowser = formatUrl(prettyHost)
return {
lanUrlForConfig,
lanUrlForTerminal,
localUrlForTerminal,
localUrlForBrowser
}
}

View File

@ -0,0 +1,23 @@
const getBaseUrl = require('./getBaseUrl')
const prefixRE = /^VUE_APP_/
module.exports = function resolveClientEnv (options, raw) {
const env = {}
Object.keys(process.env).forEach(key => {
if (prefixRE.test(key) || key === 'NODE_ENV') {
env[key] = process.env[key]
}
})
env.BASE_URL = getBaseUrl(options)
if (raw) {
return env
}
for (const key in env) {
env[key] = JSON.stringify(env[key])
}
return {
'process.env': env
}
}

View File

@ -0,0 +1,47 @@
const { chalk } = require('@vue/cli-shared-utils')
const rules = [
{
type: 'cant-resolve-loader',
re: /Can't resolve '(.*loader)'/,
msg: (e, match) => (
`Failed to resolve loader: ${chalk.yellow(match[1])}\n` +
`You may need to install it.`
)
}
]
exports.transformer = error => {
if (error.webpackError) {
const message = typeof error.webpackError === 'string'
? error.webpackError
: error.webpackError.message || ''
for (const { re, msg, type } of rules) {
const match = message.match(re)
if (match) {
return Object.assign({}, error, {
// type is necessary to avoid being printed as default error
// by friendly-error-webpack-plugin
type,
shortMessage: msg(error, match)
})
}
}
// no match, unknown webpack error without a message.
// friendly-error-webpack-plugin fails to handle this.
if (!error.message) {
return Object.assign({}, error, {
type: 'unknown-webpack-error',
shortMessage: message
})
}
}
return error
}
exports.formatter = errors => {
errors = errors.filter(e => e.shortMessage)
if (errors.length) {
return errors.map(e => e.shortMessage)
}
}

View File

@ -0,0 +1,5 @@
const path = require('path')
module.exports = function resolveLocal (...args) {
return path.join(__dirname, '../../', ...args)
}

View File

@ -0,0 +1,83 @@
const path = require('path')
const { chalk, warn, error } = require('@vue/cli-shared-utils')
const { validate } = require('../options')
function ensureSlash (config, key) {
const val = config[key]
if (typeof val === 'string') {
config[key] = val.replace(/([^/])$/, '$1/')
}
}
function removeSlash (config, key) {
if (typeof config[key] === 'string') {
config[key] = config[key].replace(/\/$/g, '')
}
}
module.exports = function resolveUserConfig ({
inlineOptions,
pkgConfig,
fileConfig,
fileConfigPath
}) {
if (fileConfig) {
if (typeof fileConfig === 'function') {
fileConfig = fileConfig()
}
if (!fileConfig || typeof fileConfig !== 'object') {
throw new Error(
`Error loading ${chalk.bold(fileConfigPath)}: ` +
`should export an object or a function that returns object.`
)
}
}
// package.vue
if (pkgConfig && typeof pkgConfig !== 'object') {
throw new Error(
`Error loading Vue CLI config in ${chalk.bold(`package.json`)}: ` +
`the "vue" field should be an object.`
)
}
let resolved, resolvedFrom
if (fileConfig) {
const configFileName = path.basename(fileConfigPath)
if (pkgConfig) {
warn(
`"vue" field in package.json ignored ` +
`due to presence of ${chalk.bold(configFileName)}.`
)
warn(
`You should migrate it into ${chalk.bold(configFileName)} ` +
`and remove it from package.json.`
)
}
resolved = fileConfig
resolvedFrom = configFileName
} else if (pkgConfig) {
resolved = pkgConfig
resolvedFrom = '"vue" field in package.json'
} else {
resolved = inlineOptions || {}
resolvedFrom = 'inline options'
}
// normalize some options
if (resolved.publicPath !== 'auto') {
ensureSlash(resolved, 'publicPath')
}
if (typeof resolved.publicPath === 'string') {
resolved.publicPath = resolved.publicPath.replace(/^\.\//, '')
}
removeSlash(resolved, 'outputDir')
// validate options
validate(resolved, msg => {
error(`Invalid options in ${chalk.bold(resolvedFrom)}: ${msg}`)
})
return resolved
}

View File

@ -0,0 +1,70 @@
// copied from @vue/babel-preset-app
const { semver } = require('@vue/cli-shared-utils')
const { default: getTargets } = require('@babel/helper-compilation-targets')
// See the result at <https://github.com/babel/babel/blob/v7.13.15/packages/babel-compat-data/data/native-modules.json>
const allModuleTargets = getTargets(
{ esmodules: true },
{ ignoreBrowserslistConfig: true }
)
function getIntersectionTargets (targets, constraintTargets) {
const intersection = Object.keys(constraintTargets).reduce(
(results, browser) => {
// exclude the browsers that the user does not need
if (!targets[browser]) {
return results
}
// if the user-specified version is higher the minimum version that supports esmodule, than use it
results[browser] = semver.gt(
semver.coerce(constraintTargets[browser]),
semver.coerce(targets[browser])
)
? constraintTargets[browser]
: targets[browser]
return results
},
{}
)
return intersection
}
function getModuleTargets (targets) {
// use the intersection of modern mode browsers and user defined targets config
return getIntersectionTargets(targets, allModuleTargets)
}
function doAllTargetsSupportModule (targets) {
const browserList = Object.keys(targets)
return browserList.every(browserName => {
if (!allModuleTargets[browserName]) {
return false
}
return semver.gte(
semver.coerce(targets[browserName]),
semver.coerce(allModuleTargets[browserName])
)
})
}
// get browserslist targets in current working directory
const projectTargets = getTargets()
const projectModuleTargets = getModuleTargets(projectTargets)
const allProjectTargetsSupportModule = doAllTargetsSupportModule(projectTargets)
module.exports = {
getTargets,
getModuleTargets,
getIntersectionTargets,
doAllTargetsSupportModule,
projectTargets,
projectModuleTargets,
allProjectTargetsSupportModule
}

View File

@ -0,0 +1,38 @@
module.exports = function validateWebpackConfig (
webpackConfig,
api,
options,
target = 'app'
) {
const singleConfig = Array.isArray(webpackConfig)
? webpackConfig[0]
: webpackConfig
const actualTargetDir = singleConfig.output.path
if (actualTargetDir !== api.resolve(options.outputDir)) {
// user directly modifies output.path in configureWebpack or chainWebpack.
// this is not supported because there's no way for us to give copy
// plugin the correct value this way.
throw new Error(
`\n\nConfiguration Error: ` +
`Avoid modifying webpack output.path directly. ` +
`Use the "outputDir" option instead.\n`
)
}
if (actualTargetDir === api.service.context) {
throw new Error(
`\n\nConfiguration Error: ` +
`Do not set output directory to project root.\n`
)
}
if (target === 'app' && singleConfig.output.publicPath !== options.publicPath) {
throw new Error(
`\n\nConfiguration Error: ` +
`Avoid modifying webpack output.publicPath directly. ` +
`Use the "publicPath" option instead.\n`
)
}
}

View File

@ -0,0 +1,69 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = class CorsPlugin {
constructor ({ publicPath, crossorigin, integrity }) {
this.crossorigin = crossorigin
this.integrity = integrity
this.publicPath = publicPath
}
apply (compiler) {
const ID = `vue-cli-cors-plugin`
compiler.hooks.compilation.tap(ID, compilation => {
const ssri = require('ssri')
const computeHash = url => {
const filename = url.replace(this.publicPath, '')
const asset = compilation.assets[filename]
if (asset) {
const src = asset.source()
const integrity = ssri.fromData(src, {
algorithms: ['sha384']
})
return integrity.toString()
}
}
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tap(ID, data => {
const tags = [...data.headTags, ...data.bodyTags]
if (this.crossorigin != null) {
tags.forEach(tag => {
if (tag.tagName === 'script' || tag.tagName === 'link') {
tag.attributes.crossorigin = this.crossorigin
}
})
}
if (this.integrity) {
tags.forEach(tag => {
if (tag.tagName === 'script') {
const hash = computeHash(tag.attributes.src)
if (hash) {
tag.attributes.integrity = hash
}
} else if (tag.tagName === 'link' && tag.attributes.rel === 'stylesheet') {
const hash = computeHash(tag.attributes.href)
if (hash) {
tag.attributes.integrity = hash
}
}
})
// when using SRI, Chrome somehow cannot reuse
// the preloaded resource, and causes the files to be downloaded twice.
// this is a Chrome bug (https://bugs.chromium.org/p/chromium/issues/detail?id=677022)
// for now we disable preload if SRI is used.
data.headTags = data.headTags.filter(tag => {
return !(
tag.tagName === 'link' &&
tag.attributes.rel === 'preload'
)
})
}
})
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tap(ID, data => {
data.html = data.html.replace(/\scrossorigin=""/g, ' crossorigin')
})
})
}
}

View File

@ -0,0 +1,223 @@
// From https://github.com/FormidableLabs/webpack-dashboard/blob/7f99b31c5f00a7818d8129cb8a8fc6eb1b71799c/plugin/index.js
// Modified by Guillaume Chau (Akryum)
/* eslint-disable max-params, max-statements */
'use strict'
const path = require('path')
const fs = require('fs-extra')
const webpack = require('webpack')
const { IpcMessenger } = require('@vue/cli-shared-utils')
const { analyzeBundle } = require('./analyzeBundle')
const ID = 'vue-cli-dashboard-plugin'
const ONE_SECOND = 1000
const FILENAME_QUERY_REGEXP = /\?.*$/
const ipc = new IpcMessenger()
function getTimeMessage (timer) {
let time = Date.now() - timer
if (time >= ONE_SECOND) {
time /= ONE_SECOND
time = Math.round(time)
time += 's'
} else {
time += 'ms'
}
return ` (${time})`
}
class DashboardPlugin {
constructor (options) {
this.type = options.type
if (this.type === 'build' && options.moduleBuild) {
this.type = 'build-modern'
}
this.watching = false
this.autoDisconnect = !options.keepAlive
}
cleanup () {
this.sendData = null
if (this.autoDisconnect) ipc.disconnect()
}
apply (compiler) {
let sendData = this.sendData
let timer
let inProgress = false
let assetSources = new Map()
if (!sendData) {
sendData = data => ipc.send({
webpackDashboardData: {
type: this.type,
value: data
}
})
}
// Progress status
let progressTime = Date.now()
const progressPlugin = new webpack.ProgressPlugin((percent, msg) => {
// in webpack 5, progress plugin will continue sending progresses even after the done hook
// for things like caching, causing the progress indicator stuck at 0.99
// so we have to use a flag to stop sending such `compiling` progress data
if (!inProgress) {
return
}
// Debouncing
const time = Date.now()
if (time - progressTime > 300) {
progressTime = time
sendData([
{
type: 'status',
value: 'Compiling'
},
{
type: 'progress',
value: percent
},
{
type: 'operations',
value: msg + getTimeMessage(timer)
}
])
}
})
progressPlugin.apply(compiler)
compiler.hooks.watchRun.tap(ID, c => {
this.watching = true
})
compiler.hooks.run.tap(ID, c => {
this.watching = false
})
compiler.hooks.compile.tap(ID, () => {
inProgress = true
timer = Date.now()
sendData([
{
type: 'status',
value: 'Compiling'
},
{
type: 'progress',
value: 0
}
])
})
compiler.hooks.invalid.tap(ID, () => {
sendData([
{
type: 'status',
value: 'Invalidated'
},
{
type: 'progress',
value: 0
},
{
type: 'operations',
value: 'idle'
}
])
})
compiler.hooks.failed.tap(ID, () => {
sendData([
{
type: 'status',
value: 'Failed'
},
{
type: 'operations',
value: `idle${getTimeMessage(timer)}`
}
])
inProgress = false
})
compiler.hooks.afterEmit.tap(ID, compilation => {
assetSources = new Map()
for (const name in compilation.assets) {
const asset = compilation.assets[name]
const filename = name.replace(FILENAME_QUERY_REGEXP, '')
try {
assetSources.set(filename, asset.source())
} catch (e) {
const webpackFs = compiler.outputFileSystem
const fullPath = (webpackFs.join || path.join)(compiler.options.output.path, filename)
const buf = webpackFs.readFileSync(fullPath)
assetSources.set(filename, buf.toString())
}
}
})
compiler.hooks.done.tap(ID, stats => {
let statsData = stats.toJson()
// Sometimes all the information is located in `children` array
if ((!statsData.assets || !statsData.assets.length) && statsData.children && statsData.children.length) {
statsData = statsData.children[0]
}
const outputPath = compiler.options.output.path
statsData.assets.forEach(asset => {
// Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '')
asset.fullPath = path.join(outputPath, asset.name)
})
// Analyze the assets and update sizes on assets and modules
analyzeBundle(statsData, assetSources)
const hasErrors = stats.hasErrors()
sendData([
{
type: 'status',
value: hasErrors ? 'Failed' : 'Success'
},
{
type: 'progress',
value: 1
},
{
type: 'operations',
value: `idle${getTimeMessage(timer)}`
}
])
inProgress = false
const statsFile = path.resolve(process.cwd(), `./node_modules/.stats-${this.type}.json`)
fs.writeJson(statsFile, {
errors: hasErrors,
warnings: stats.hasWarnings(),
data: statsData
}).then(() => {
sendData([
{
type: 'stats'
}
])
if (!this.watching) {
this.cleanup()
}
}).catch(error => {
console.error(error)
})
})
}
}
module.exports = DashboardPlugin

View File

@ -0,0 +1,86 @@
const fs = require('fs-extra')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
class ModernModePlugin {
constructor ({ targetDir, isModuleBuild }) {
this.targetDir = targetDir
this.isModuleBuild = isModuleBuild
}
apply (compiler) {
if (!this.isModuleBuild) {
this.applyLegacy(compiler)
} else {
this.applyModule(compiler)
}
}
applyLegacy (compiler) {
const ID = `vue-cli-legacy-bundle`
compiler.hooks.compilation.tap(ID, compilation => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(ID, async (data, cb) => {
// get stats, write to disk
await fs.ensureDir(this.targetDir)
const htmlName = path.basename(data.plugin.options.filename)
// Watch out for output files in sub directories
const htmlPath = path.dirname(data.plugin.options.filename)
const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
await fs.mkdirp(path.dirname(tempFilename))
let tags = data.bodyTags
if (data.plugin.options.scriptLoading === 'defer') {
tags = data.headTags
}
await fs.writeFile(tempFilename, JSON.stringify(tags))
cb()
})
})
}
applyModule (compiler) {
const ID = `vue-cli-modern-bundle`
compiler.hooks.compilation.tap(ID, compilation => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(ID, async (data, cb) => {
let tags = data.bodyTags
if (data.plugin.options.scriptLoading === 'defer') {
tags = data.headTags
}
// use <script type="module"> for modern assets
tags.forEach(tag => {
if (tag.tagName === 'script' && tag.attributes) {
tag.attributes.type = 'module'
}
})
// use <link rel="modulepreload"> instead of <link rel="preload">
// for modern assets
data.headTags.forEach(tag => {
if (tag.tagName === 'link' &&
tag.attributes.rel === 'preload' &&
tag.attributes.as === 'script') {
tag.attributes.rel = 'modulepreload'
}
})
// inject links for legacy assets as <script nomodule>
const htmlName = path.basename(data.plugin.options.filename)
// Watch out for output files in sub directories
const htmlPath = path.dirname(data.plugin.options.filename)
const tempFilename = path.join(this.targetDir, htmlPath, `legacy-assets-${htmlName}.json`)
const legacyAssets = JSON.parse(await fs.readFile(tempFilename, 'utf-8'))
.filter(a => a.tagName === 'script' && a.attributes)
legacyAssets.forEach(a => { a.attributes.nomodule = '' })
tags.push(...legacyAssets)
await fs.remove(tempFilename)
cb()
})
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tap(ID, data => {
data.html = data.html.replace(/\snomodule="">/g, ' nomodule>')
})
})
}
}
module.exports = ModernModePlugin

View File

@ -0,0 +1,16 @@
const fs = require('fs-extra')
module.exports = class MovePlugin {
constructor (from, to) {
this.from = from
this.to = to
}
apply (compiler) {
compiler.hooks.done.tap('move-plugin', () => {
if (fs.existsSync(this.from)) {
fs.moveSync(this.from, this.to, { overwrite: true })
}
})
}
}

View File

@ -0,0 +1,71 @@
// https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
const safariFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();`
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { semver } = require('@vue/cli-shared-utils')
const { projectModuleTargets } = require('../util/targets')
const minSafariVersion = projectModuleTargets.safari
const minIOSVersion = projectModuleTargets.ios
const supportsSafari10 =
(minSafariVersion && semver.lt(semver.coerce(minSafariVersion), '11.0.0')) ||
(minIOSVersion && semver.lt(semver.coerce(minIOSVersion), '11.0.0'))
const needsSafariFix = supportsSafari10
class SafariNomoduleFixPlugin {
constructor ({ unsafeInline, jsDirectory }) {
this.unsafeInline = unsafeInline
this.jsDirectory = jsDirectory
}
apply (compiler) {
if (!needsSafariFix) {
return
}
const { RawSource } = compiler.webpack
? compiler.webpack.sources
: require('webpack-sources')
const ID = 'SafariNomoduleFixPlugin'
compiler.hooks.compilation.tap(ID, compilation => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tap(ID, data => {
let scriptTag
if (this.unsafeInline) {
// inject inline Safari 10 nomodule fix
scriptTag = {
tagName: 'script',
closeTag: true,
innerHTML: safariFix
}
} else {
// inject the fix as an external script
const safariFixPath = path.join(this.jsDirectory, 'safari-nomodule-fix.js')
const fullSafariFixPath = path.join(compilation.options.output.publicPath, safariFixPath)
compilation.assets[safariFixPath] = new RawSource(safariFix)
scriptTag = {
tagName: 'script',
closeTag: true,
attributes: {
src: fullSafariFixPath
}
}
}
let tags = data.bodyTags
if (data.plugin.options.scriptLoading === 'defer') {
tags = data.headTags
}
// insert just before the first actual script tag,
// and after all other tags such as `meta`
const firstScriptIndex = tags.findIndex(tag => tag.tagName === 'script')
tags.splice(firstScriptIndex, 0, scriptTag)
})
})
}
}
SafariNomoduleFixPlugin.safariFix = safariFix
module.exports = SafariNomoduleFixPlugin

View File

@ -0,0 +1,446 @@
// From https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/4abac503c789bac94118e5bbfc410686fb5112c7/src/parseUtils.js
// Modified by Guillaume Chau (Akryum)
const acorn = require('acorn')
const walk = require('acorn-walk')
const mapValues = require('lodash.mapvalues')
const zlib = require('zlib')
const { warn } = require('@vue/cli-shared-utils')
exports.analyzeBundle = function analyzeBundle (bundleStats, assetSources) {
// Picking only `*.js` assets from bundle that has non-empty `chunks` array
const jsAssets = []
const otherAssets = []
// Separate JS assets
bundleStats.assets.forEach(asset => {
if (asset.name.endsWith('.js') && asset.chunks && asset.chunks.length) {
jsAssets.push(asset)
} else {
otherAssets.push(asset)
}
})
// Trying to parse bundle assets and get real module sizes
let bundlesSources = null
let parsedModules = null
bundlesSources = {}
parsedModules = {}
for (const asset of jsAssets) {
const source = assetSources.get(asset.name)
let bundleInfo
try {
bundleInfo = parseBundle(source)
} catch (err) {
const msg = (err.code === 'ENOENT') ? 'no such file' : err.message
warn(`Error parsing bundle asset "${asset.fullPath}": ${msg}`)
continue
}
bundlesSources[asset.name] = bundleInfo.src
Object.assign(parsedModules, bundleInfo.modules)
}
if (!Object.keys(bundlesSources).length) {
bundlesSources = null
parsedModules = null
warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n')
}
// Update sizes
bundleStats.modules.forEach(module => {
const parsedSrc = parsedModules && parsedModules[module.id]
module.size = {
stats: module.size
}
if (parsedSrc) {
module.size.parsed = parsedSrc.length
module.size.gzip = getGzipSize(parsedSrc)
} else {
module.size.parsed = module.size.stats
module.size.gzip = 0
}
})
jsAssets.forEach(asset => {
const src = bundlesSources && bundlesSources[asset.name]
asset.size = {
stats: asset.size
}
if (src) {
asset.size.parsed = src.length
asset.size.gzip = getGzipSize(src)
} else {
asset.size.parsed = asset.size.stats
asset.size.gzip = 0
}
}, {})
otherAssets.forEach(asset => {
const src = assetSources.get(asset.name)
asset.size = {
stats: asset.size,
parsed: asset.size
}
if (src) {
asset.size.gzip = getGzipSize(src)
} else {
asset.size.gzip = 0
}
})
}
function parseBundle (bundleContent) {
const ast = acorn.parse(bundleContent, {
sourceType: 'script',
// I believe in a bright future of ECMAScript!
// Actually, it's set to `2050` to support the latest ECMAScript version that currently exists.
// Seems like `acorn` supports such weird option value.
ecmaVersion: 2050
})
const walkState = {
locations: null,
expressionStatementDepth: 0
}
walk.recursive(
ast,
walkState,
{
ExpressionStatement (node, state, c) {
if (state.locations) return
state.expressionStatementDepth++
if (
// Webpack 5 stores modules in the the top-level IIFE
state.expressionStatementDepth === 1 &&
ast.body.includes(node) &&
isIIFE(node)
) {
const fn = getIIFECallExpression(node)
if (
// It should not contain neither arguments
fn.arguments.length === 0 &&
// ...nor parameters
fn.callee.params.length === 0
) {
// Modules are stored in the very first variable declaration as hash
const firstVariableDeclaration = fn.callee.body.body.find(n => n.type === 'VariableDeclaration')
if (firstVariableDeclaration) {
for (const declaration of firstVariableDeclaration.declarations) {
if (declaration.init) {
state.locations = getModulesLocations(declaration.init)
if (state.locations) {
break
}
}
}
}
}
}
if (!state.locations) {
c(node.expression, state)
}
state.expressionStatementDepth--
},
AssignmentExpression (node, state) {
if (state.locations) return
// Modules are stored in exports.modules:
// exports.modules = {};
const { left, right } = node
if (
left &&
left.object && left.object.name === 'exports' &&
left.property && left.property.name === 'modules' &&
isModulesHash(right)
) {
state.locations = getModulesLocations(right)
}
},
CallExpression (node, state, c) {
if (state.locations) return
const args = node.arguments
// Main chunk with webpack loader.
// Modules are stored in first argument:
// (function (...) {...})(<modules>)
if (
node.callee.type === 'FunctionExpression' &&
!node.callee.id &&
args.length === 1 &&
isSimpleModulesList(args[0])
) {
state.locations = getModulesLocations(args[0])
return
}
// Async Webpack < v4 chunk without webpack loader.
// webpackJsonp([<chunks>], <modules>, ...)
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (
node.callee.type === 'Identifier' &&
mayBeAsyncChunkArguments(args) &&
isModulesList(args[1])
) {
state.locations = getModulesLocations(args[1])
return
}
// Async Webpack v4 chunk without webpack loader.
// (window.webpackJsonp=window.webpackJsonp||[]).push([[<chunks>], <modules>, ...]);
// As function name may be changed with `output.jsonpFunction` option we can't rely on it's default name.
if (isAsyncChunkPushExpression(node)) {
state.locations = getModulesLocations(args[0].elements[1])
return
}
// Webpack v4 WebWorkerChunkTemplatePlugin
// globalObject.chunkCallbackName([<chunks>],<modules>, ...);
// Both globalObject and chunkCallbackName can be changed through the config, so we can't check them.
if (isAsyncWebWorkerChunkExpression(node)) {
state.locations = getModulesLocations(args[1])
return
}
// Walking into arguments because some of plugins (e.g. `DedupePlugin`) or some Webpack
// features (e.g. `umd` library output) can wrap modules list into additional IIFE.
args.forEach(arg => c(arg, state))
}
}
)
let modules
if (walkState.locations) {
modules = mapValues(walkState.locations,
loc => bundleContent.slice(loc.start, loc.end)
)
} else {
modules = {}
}
return {
modules: modules,
src: bundleContent,
runtimeSrc: getBundleRuntime(bundleContent, walkState.locations)
}
}
function getGzipSize (buffer) {
return zlib.gzipSync(buffer).length
}
/**
* Returns bundle source except modules
*/
function getBundleRuntime (content, modulesLocations) {
const sortedLocations = Object.values(modulesLocations || {})
.sort((a, b) => a.start - b.start)
let result = ''
let lastIndex = 0
for (const { start, end } of sortedLocations) {
result += content.slice(lastIndex, start)
lastIndex = end
}
return result + content.slice(lastIndex, content.length)
}
function isIIFE (node) {
return (
node.type === 'ExpressionStatement' &&
(
node.expression.type === 'CallExpression' ||
(node.expression.type === 'UnaryExpression' && node.expression.argument.type === 'CallExpression')
)
)
}
function getIIFECallExpression (node) {
if (node.expression.type === 'UnaryExpression') {
return node.expression.argument
} else {
return node.expression
}
}
function isModulesList (node) {
return (
isSimpleModulesList(node) ||
// Modules are contained in expression `Array([minimum ID]).concat([<module>, <module>, ...])`
isOptimizedModulesArray(node)
)
}
function isSimpleModulesList (node) {
return (
// Modules are contained in hash. Keys are module ids.
isModulesHash(node) ||
// Modules are contained in array. Indexes are module ids.
isModulesArray(node)
)
}
function isModulesHash (node) {
return (
node.type === 'ObjectExpression' &&
node.properties
.map(p => p.value)
.every(isModuleWrapper)
)
}
function isModulesArray (node) {
return (
node.type === 'ArrayExpression' &&
node.elements.every(elem =>
// Some of array items may be skipped because there is no module with such id
!elem ||
isModuleWrapper(elem)
)
)
}
function isOptimizedModulesArray (node) {
// Checking whether modules are contained in `Array(<minimum ID>).concat(...modules)` array:
// https://github.com/webpack/webpack/blob/v1.14.0/lib/Template.js#L91
// The `<minimum ID>` + array indexes are module ids
return (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
// Make sure the object called is `Array(<some number>)`
node.callee.object.type === 'CallExpression' &&
node.callee.object.callee.type === 'Identifier' &&
node.callee.object.callee.name === 'Array' &&
node.callee.object.arguments.length === 1 &&
isNumericId(node.callee.object.arguments[0]) &&
// Make sure the property X called for `Array(<some number>).X` is `concat`
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'concat' &&
// Make sure exactly one array is passed in to `concat`
node.arguments.length === 1 &&
isModulesArray(node.arguments[0])
)
}
function isModuleWrapper (node) {
return (
// It's an anonymous function expression that wraps module
((node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') && !node.id) ||
// If `DedupePlugin` is used it can be an ID of duplicated module...
isModuleId(node) ||
// or an array of shape [<module_id>, ...args]
(node.type === 'ArrayExpression' && node.elements.length > 1 && isModuleId(node.elements[0]))
)
}
function isModuleId (node) {
return (node.type === 'Literal' && (isNumericId(node) || typeof node.value === 'string'))
}
function isNumericId (node) {
return (node.type === 'Literal' && Number.isInteger(node.value) && node.value >= 0)
}
function isChunkIds (node) {
// Array of numeric or string ids. Chunk IDs are strings when NamedChunksPlugin is used
return (
node.type === 'ArrayExpression' &&
node.elements.every(isModuleId)
)
}
function isAsyncChunkPushExpression (node) {
const {
callee,
arguments: args
} = node
return (
callee.type === 'MemberExpression' &&
callee.property.name === 'push' &&
callee.object.type === 'AssignmentExpression' &&
args.length === 1 &&
args[0].type === 'ArrayExpression' &&
mayBeAsyncChunkArguments(args[0].elements) &&
isModulesList(args[0].elements[1])
)
}
function mayBeAsyncChunkArguments (args) {
return (
args.length >= 2 &&
isChunkIds(args[0])
)
}
function isAsyncWebWorkerChunkExpression (node) {
const { callee, type, arguments: args } = node
return (
type === 'CallExpression' &&
callee.type === 'MemberExpression' &&
args.length === 2 &&
isChunkIds(args[0]) &&
isModulesList(args[1])
)
}
function getModulesLocations (node) {
if (node.type === 'ObjectExpression') {
// Modules hash
const modulesNodes = node.properties
return modulesNodes.reduce((result, moduleNode) => {
const moduleId = moduleNode.key.name || moduleNode.key.value
result[moduleId] = getModuleLocation(moduleNode.value)
return result
}, {})
}
const isOptimizedArray = (node.type === 'CallExpression')
if (node.type === 'ArrayExpression' || isOptimizedArray) {
// Modules array or optimized array
const minId = isOptimizedArray
// Get the [minId] value from the Array() call first argument literal value
? node.callee.object.arguments[0].value
// `0` for simple array
: 0
const modulesNodes = isOptimizedArray
// The modules reside in the `concat()` function call arguments
? node.arguments[0].elements
: node.elements
return modulesNodes.reduce((result, moduleNode, i) => {
if (moduleNode) {
result[i + minId] = getModuleLocation(moduleNode)
}
return result
}, {})
}
return {}
}
function getModuleLocation (node) {
return { start: node.start, end: node.end }
}