mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-04-22 04:18:33 -05:00
feat(ui): better build progress
This commit is contained in:
@@ -100,6 +100,10 @@ class DashboardPlugin {
|
||||
{
|
||||
type: 'status',
|
||||
value: 'Compiling'
|
||||
},
|
||||
{
|
||||
type: 'progress',
|
||||
value: 0
|
||||
}
|
||||
])
|
||||
})
|
||||
@@ -167,7 +171,7 @@ class DashboardPlugin {
|
||||
},
|
||||
{
|
||||
type: 'progress',
|
||||
value: 0
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
type: 'operations',
|
||||
|
||||
@@ -1,32 +1,70 @@
|
||||
<template>
|
||||
<div class="build-progress">
|
||||
<div
|
||||
class="build-progress"
|
||||
:class="{
|
||||
[`mode-${mode}`]: true
|
||||
}"
|
||||
>
|
||||
<div class="content">
|
||||
<loading-progress
|
||||
:progress="progress"
|
||||
size="128"
|
||||
counter-clockwise
|
||||
/>
|
||||
<div class="progress-wrapper">
|
||||
<transition-group
|
||||
name="vue-ui-fade"
|
||||
class="progress-bars"
|
||||
>
|
||||
<div
|
||||
v-for="(key, index) of Object.keys(progress)"
|
||||
:key="key"
|
||||
class="progress-bar-wrapper"
|
||||
>
|
||||
<loading-progress
|
||||
:key="key"
|
||||
:progress="progress[key]"
|
||||
:size="128 - 16 * index"
|
||||
class="progress-bar"
|
||||
counter-clockwise
|
||||
:class="{
|
||||
'disable-animation': progress[key] === 0,
|
||||
[`mode-${key}`]: true
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</transition-group>
|
||||
|
||||
<div class="progress">
|
||||
{{ typeof progress === 'number' ? Math.round(progress * 100) : 0 }}
|
||||
<div class="progress">
|
||||
<div
|
||||
class="progress-animation"
|
||||
:class="{
|
||||
active: status && status !== 'Idle'
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-for="n in 4"
|
||||
:key="n"
|
||||
class="animation"
|
||||
:style="{
|
||||
'animation-delay': `${n * 0.25}s`
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition duration="500">
|
||||
<div v-if="status === 'Success'" class="status-icon done">
|
||||
<div class="wrapper">
|
||||
<VueIcon icon="check_circle"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition duration="500">
|
||||
<div v-if="status === 'Failed'" class="status-icon error">
|
||||
<div class="wrapper">
|
||||
<VueIcon icon="error"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<transition duration="500">
|
||||
<div v-if="status === 'Success'" class="status-icon done">
|
||||
<div class="wrapper">
|
||||
<VueIcon icon="check_circle"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition duration="500">
|
||||
<div v-if="status === 'Failed'" class="status-icon error">
|
||||
<div class="wrapper">
|
||||
<VueIcon icon="error"/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<div class="operations">
|
||||
<span v-if="operations">{{ operations }}</span>
|
||||
<VueIcon
|
||||
@@ -43,18 +81,23 @@
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'mode'
|
||||
])
|
||||
},
|
||||
|
||||
sharedData () {
|
||||
return mapSharedData('org.vue.webpack.', {
|
||||
status: `${this.mode}-status`,
|
||||
progress: `${this.mode}-progress`,
|
||||
rawProgress: `${this.mode}-progress`,
|
||||
operations: `${this.mode}-operations`
|
||||
})
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'mode'
|
||||
]),
|
||||
|
||||
progress () {
|
||||
const raw = this.rawProgress
|
||||
return raw && Object.keys(raw).length ? raw : { unknown: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -87,6 +130,12 @@ export default {
|
||||
&:first-letter
|
||||
text-transform uppercase
|
||||
|
||||
.progress-wrapper
|
||||
width 178px
|
||||
height @width
|
||||
position relative
|
||||
|
||||
.progress-bar-wrapper,
|
||||
.progress,
|
||||
.status-icon
|
||||
h-box()
|
||||
@@ -96,12 +145,36 @@ export default {
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
height 178px
|
||||
height @width
|
||||
|
||||
.progress
|
||||
color lighten($vue-ui-color-dark, 60%)
|
||||
font-weight lighter
|
||||
font-size 42px
|
||||
.progress-bar
|
||||
&.disable-animation
|
||||
>>> path
|
||||
transition none
|
||||
&.mode-build-modern
|
||||
>>> .progress
|
||||
stroke $vue-ui-color-info
|
||||
|
||||
.progress-animation
|
||||
display grid
|
||||
$size = 12px
|
||||
grid-template-columns repeat(2, $size)
|
||||
grid-template-rows repeat(2, $size)
|
||||
grid-template-areas "z1 z4" "z2 z3"
|
||||
grid-gap 12px
|
||||
.animation
|
||||
width 100%
|
||||
height @width
|
||||
border-radius 50%
|
||||
background rgba(black, .1)
|
||||
transition background .15s
|
||||
for n in (1..4)
|
||||
&:nth-child({n})
|
||||
grid-area 'z%s' % n
|
||||
&.active
|
||||
.animation
|
||||
background $vue-ui-color-primary
|
||||
animation progress 1s infinite
|
||||
|
||||
.status-icon
|
||||
.wrapper
|
||||
@@ -161,4 +234,25 @@ export default {
|
||||
opacity 0
|
||||
&.v-leave-to
|
||||
opacity 0
|
||||
|
||||
&.mode-build-modern
|
||||
.progress-animation.active
|
||||
.animation
|
||||
background $vue-ui-color-info
|
||||
|
||||
.status-icon
|
||||
&.done
|
||||
.wrapper::before
|
||||
background $vue-ui-color-info
|
||||
>>> svg
|
||||
fill $vue-ui-color-info
|
||||
|
||||
@keyframes progress
|
||||
0%,
|
||||
30%
|
||||
transform none
|
||||
50%
|
||||
transform scale(1.5)
|
||||
80%
|
||||
transform none
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="title">{{ $t('org.vue.vue-webpack.analyzer.title') }}</div>
|
||||
|
||||
<VueSwitch
|
||||
v-if="modernMode"
|
||||
v-if="mode !== 'serve' && modernMode"
|
||||
v-model="showModernBuild"
|
||||
>
|
||||
{{ $t('org.vue.vue-webpack.modern-mode') }}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<VueSwitch
|
||||
v-if="modernMode"
|
||||
v-if="mode !== 'serve' && modernMode"
|
||||
v-model="showModernBuild"
|
||||
>
|
||||
{{ $t('org.vue.vue-webpack.modern-mode') }}
|
||||
|
||||
@@ -6,38 +6,57 @@ module.exports = api => {
|
||||
|
||||
let firstRun = true
|
||||
let hadFailed = false
|
||||
let modernMode = false
|
||||
|
||||
// Specific to each modes (serve, build, ...)
|
||||
const fields = {
|
||||
status: null,
|
||||
progress: 0,
|
||||
progress: {},
|
||||
operations: null,
|
||||
stats: null,
|
||||
sizes: null,
|
||||
problems: null,
|
||||
url: null
|
||||
}
|
||||
|
||||
// Common fields for all mode
|
||||
const commonFields = {
|
||||
'modern-mode': false
|
||||
}
|
||||
|
||||
// Called when opening a project
|
||||
function setupSharedData (mode) {
|
||||
resetSharedData(mode)
|
||||
for (const field in fields) {
|
||||
const id = `${mode}-${field}`
|
||||
watchSharedData(id, (value) => {
|
||||
const project = api.getProject()
|
||||
if (project) {
|
||||
setSharedData(`${project.id}-${id}`, value)
|
||||
}
|
||||
})
|
||||
watchData(id)
|
||||
}
|
||||
}
|
||||
|
||||
// Called when opening a project
|
||||
function setupCommonData () {
|
||||
for (const field in commonFields) {
|
||||
setSharedData(field, getSharedDataInitialValue(field, commonFields[field]))
|
||||
watchData(field)
|
||||
}
|
||||
}
|
||||
|
||||
function resetSharedData (mode, clear = false) {
|
||||
for (const field in fields) {
|
||||
const id = `${mode}-${field}`
|
||||
setSharedData(id, getSharedDataInitialValue(id, field, clear))
|
||||
setSharedData(id, getSharedDataInitialValue(id, fields[field], clear))
|
||||
}
|
||||
}
|
||||
|
||||
function getSharedDataInitialValue (id, field, clear) {
|
||||
function watchData (id) {
|
||||
watchSharedData(id, (value) => {
|
||||
const project = api.getProject()
|
||||
if (project) {
|
||||
setSharedData(`${project.id}-${id}`, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getSharedDataInitialValue (id, defaultValue, clear) {
|
||||
if (!clear) {
|
||||
const project = api.getProject()
|
||||
if (project) {
|
||||
@@ -45,29 +64,50 @@ module.exports = api => {
|
||||
if (data != null) return data.value
|
||||
}
|
||||
}
|
||||
return fields[field]
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
async function onWebpackMessage ({ data: message }) {
|
||||
if (message.webpackDashboardData) {
|
||||
const modernMode = getSharedData('modern-mode').value
|
||||
const type = message.webpackDashboardData.type
|
||||
|
||||
for (const data of message.webpackDashboardData.value) {
|
||||
const id = `${type}-${data.type}`
|
||||
|
||||
if (data.type === 'stats') {
|
||||
// Stats are read from a file
|
||||
const statsFile = path.resolve(process.cwd(), `./node_modules/.stats-${type}.json`)
|
||||
const value = await fs.readJson(statsFile)
|
||||
setSharedData(`${type}-${data.type}`, value)
|
||||
setSharedData(id, value)
|
||||
await fs.remove(statsFile)
|
||||
} else if (type.indexOf('build') !== -1 && modernMode && data.type === 'progress') {
|
||||
// Progress is shared between 'build' and 'build-modern'
|
||||
// 'build' first and then 'build-modern'
|
||||
const value = type === 'build' ? data.value / 2 : (data.value + 1) / 2
|
||||
// We display the same progress bar for both
|
||||
for (const t of ['build', 'build-modern']) {
|
||||
setSharedData(`${t}-${data.type}`, value)
|
||||
} else if (data.type === 'progress') {
|
||||
if (type === 'serve' || !modernMode) {
|
||||
setSharedData(id, {
|
||||
[type]: data.value
|
||||
})
|
||||
} else {
|
||||
// Display two progress bars
|
||||
const progress = getSharedData(id).value
|
||||
progress[type] = data.value
|
||||
for (const t of ['build', 'build-modern']) {
|
||||
setSharedData(`${t}-${data.type}`, {
|
||||
build: progress.build || 0,
|
||||
'build-modern': progress['build-modern'] || 0
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setSharedData(`${type}-${data.type}`, data.value)
|
||||
// Don't display success until both build and build-modern are done
|
||||
if (type !== 'serve' && modernMode && data.type === 'status' && data.value === 'Success') {
|
||||
if (type === 'build-modern') {
|
||||
for (const t of ['build', 'build-modern']) {
|
||||
setSharedData(`${t}-status`, data.value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setSharedData(id, data.value)
|
||||
}
|
||||
|
||||
// Notifications
|
||||
if (type === 'serve' && data.type === 'status') {
|
||||
@@ -106,6 +146,7 @@ module.exports = api => {
|
||||
for (const key of ['serve', 'build', 'build-modern']) {
|
||||
setupSharedData(key)
|
||||
}
|
||||
setupCommonData()
|
||||
})
|
||||
|
||||
// Tasks
|
||||
@@ -285,7 +326,7 @@ module.exports = api => {
|
||||
if (answers.name) args.push('--port', answers.name)
|
||||
if (answers.watch) args.push('--watch')
|
||||
if (answers.modern) args.push('--modern')
|
||||
setSharedData('modern-mode', modernMode = !!answers.modern)
|
||||
setSharedData('modern-mode', !!answers.modern)
|
||||
args.push('--dashboard')
|
||||
|
||||
// Data
|
||||
@@ -341,11 +382,19 @@ module.exports = api => {
|
||||
}
|
||||
})
|
||||
|
||||
// Webpack dashboard
|
||||
api.addClientAddon({
|
||||
id: 'org.vue.webpack.client-addon',
|
||||
path: '@vue/cli-ui-addon-webpack/dist'
|
||||
})
|
||||
if (process.env.VUE_APP_CLI_UI_DEV) {
|
||||
// Add dynamic components in dev mode (webpack dashboard & analyzer)
|
||||
api.addClientAddon({
|
||||
id: 'org.vue.webpack.client-addon.dev',
|
||||
url: 'http://localhost:8096/index.js'
|
||||
})
|
||||
} else {
|
||||
// Webpack dashboard
|
||||
api.addClientAddon({
|
||||
id: 'org.vue.webpack.client-addon',
|
||||
path: '@vue/cli-ui-addon-webpack/dist'
|
||||
})
|
||||
}
|
||||
|
||||
// Open app button
|
||||
api.ipcOn(({ data }) => {
|
||||
|
||||
@@ -5,12 +5,6 @@ module.exports = api => {
|
||||
console.log('has(eslint)', api.hasPlugin('eslint'))
|
||||
console.log('has(typescript)', api.hasPlugin('typescript'))
|
||||
|
||||
// Add dynamic components in dev mode (webpack dashboard & analyzer)
|
||||
api.addClientAddon({
|
||||
id: 'org.vue.webpack.client-addon.dev',
|
||||
url: 'http://localhost:8096/index.js'
|
||||
})
|
||||
|
||||
// Add a test page below 'plugins', 'configurations' and 'tasks' on the left sidebar
|
||||
api.addView({
|
||||
id: 'org.vue.webpack.views.test',
|
||||
|
||||
Reference in New Issue
Block a user