10 Commits

Author SHA1 Message Date
Gennady Grishkovtsov
7e89d8a33a Update version to 2.2.0 2018-10-05 22:52:39 +03:00
Gennady Grishkovtsov
420cf2e194 Update readme 2018-10-05 22:51:55 +03:00
Gennady Grishkovtsov
52b7dfe958 Add custom header & minor refactoring 2018-10-05 22:51:41 +03:00
Gennady Grishkovtsov
5d8139d674 Update package.json & webpack 2018-10-05 22:51:20 +03:00
Gennady Grishkovtsov
206216643a Update version to 2.1.0 2018-09-30 16:01:51 +03:00
Gennady Grishkovtsov
fc0c9c824a Add record removing feature 2018-09-30 15:54:50 +03:00
Gennady Grishkovtsov
261d7a80ec Add decorator for all player buttons 2018-09-30 15:54:50 +03:00
Gennady Grishkovtsov
860f7e6158 Add random ID for each record 2018-09-30 15:54:42 +03:00
Gennady Grishkovtsov
83ccce2374 Merge pull request #1 from Tomotoes/var-fs
Add :key attribute for "v-for" to recorder.vue
2018-08-20 11:59:20 +03:00
JinmaQAQ
cb7a410ae5 Modify recorder.vue 2018-08-20 11:14:14 +08:00
16 changed files with 300 additions and 227 deletions

View File

@@ -15,7 +15,7 @@
- A lot of callbacks - A lot of callbacks
- Individual an audio player - Individual an audio player
### Tested in ### Tested in (desktop)
- Chrome - Chrome
- Firefox - Firefox
@@ -33,6 +33,7 @@ npm i vue-audio-recorder --save
| --------------------- | -------- | --------------------------------------------------------------- | | --------------------- | -------- | --------------------------------------------------------------- |
| attempts | Number | Number of recording attempts | | attempts | Number | Number of recording attempts |
| compact | Boolean | Hide the download and upload buttons | | compact | Boolean | Hide the download and upload buttons |
| headers | Object | HTTP headers |
| time | Number | Time limit for the record (minutes) | | time | Number | Time limit for the record (minutes) |
| upload-url | String | URL for uploading | | upload-url | String | URL for uploading |
| start-record | Function | Fires after click the record button | | start-record | Function | Fires after click the record button |
@@ -52,17 +53,6 @@ npm i vue-audio-recorder --save
## Usage ## Usage
The most common use case is to register the component globally
```js
import {AudioRecorder, AudioPlayer} from 'vue-audio-recorder'
Vue.component(AudioPlayer)
Vue.component(AudioRecorder)
```
Alternatively you can do this to register the components
```js ```js
import AudioRecorder from 'vue-audio-recorder' import AudioRecorder from 'vue-audio-recorder'
@@ -73,6 +63,7 @@ Alternatively you can do this to register the components
<audio-recorder <audio-recorder
upload-url="YOUR_API_URL" upload-url="YOUR_API_URL"
:attempts="3" :attempts="3"
:headers="headers"
:time="2" :time="2"
:start-record="callback" :start-record="callback"
:stop-record="callback" :stop-record="callback"

View File

@@ -13,6 +13,7 @@
upload-url="some url" upload-url="some url"
:attempts="3" :attempts="3"
:time="2" :time="2"
:headers="headers"
:start-record="callback" :start-record="callback"
:stop-record="callback" :stop-record="callback"
:start-upload="callback" :start-upload="callback"
@@ -24,19 +25,15 @@
</template> </template>
<script> <script>
import AudioPlayer from '../src/components/player'
import AudioRecorder from '../src/components/recorder'
export default { export default {
name: 'app', name: 'app',
components: {
AudioPlayer,
AudioRecorder
},
data () { data () {
return { return {
mp3: '/demo/example.mp3', mp3: '/demo/example.mp3',
showRecorder: true showRecorder: true,
headers: {
'X-Custom-Header': 'some data'
}
} }
}, },
methods: { methods: {

View File

@@ -2,8 +2,12 @@ import Vue from 'vue'
import axios from 'axios' import axios from 'axios'
import app from './app' import app from './app'
import AudioRecorder from '@/index'
Vue.prototype.$http = axios Vue.prototype.$http = axios
Vue.use(AudioRecorder)
new Vue({ new Vue({
el: '#app', el: '#app',
render: h => h(app) render: h => h(app)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,12 @@
{ {
"name": "vue-audio-recorder", "name": "vue-audio-recorder",
"description": "Audio recorder for Vue.js. It allows to create, play, download and store records on a server", "description": "Audio recorder for Vue.js. It allows to create, play, download and store records on a server",
"version": "2.0.0", "version": "2.2.0",
"author": "Gennady Grishkovtsov <grishkovelli@gmail.com>", "author": "Gennady Grishkovtsov <grishkovelli@gmail.com>",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot --https", "dev": "webpack-dev-server --env.NODE_ENV=development --mode development --open --hot --https",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules" "build": "webpack --env.NODE_ENV=production --mode production --progress --hide-modules"
}, },
"dependencies": {}, "dependencies": {},
"browserslist": [ "browserslist": [
@@ -26,11 +26,13 @@
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"node-sass": "^4.5.3", "node-sass": "^4.5.3",
"sass-loader": "^6.0.6", "sass-loader": "^6.0.6",
"uglifyjs-webpack-plugin": "^2.0.1",
"vue": "^2.5.16", "vue": "^2.5.16",
"vue-loader": "^13.0.5", "vue-loader": "^14.2.2",
"vue-template-compiler": "^2.4.4", "vue-template-compiler": "^2.4.4",
"webpack": "^3.6.0", "webpack": "^4.17.1",
"webpack-dev-server": "^2.9.1", "webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.9",
"webpack-merge": "^4.1.3" "webpack-merge": "^4.1.3"
}, },
"main": "dist/vue-audio-recorder.min.js", "main": "dist/vue-audio-recorder.min.js",

View File

@@ -24,7 +24,7 @@
</template> </template>
<script> <script>
import { calculateLineHeadPosition } from '@/lib/utils.js' import { calculateLineHeadPosition } from '@/lib/utils'
export default { export default {
props: { props: {

View File

@@ -88,20 +88,20 @@
id="download" id="download"
class="ar-icon ar-icon__sm" class="ar-icon ar-icon__sm"
name="download" name="download"
@click.native="download"/> @click.native="decorator(download)"/>
<icon-button <icon-button
id="play" id="play"
class="ar-icon ar-icon__lg ar-player__play" class="ar-icon ar-icon__lg ar-player__play"
:name="playBtnIcon" :name="playBtnIcon"
:class="{'ar-player__play--active': isPlaying}" :class="{'ar-player__play--active': isPlaying}"
@click.native="playback"/> @click.native="decorator(playback)"/>
<icon-button <uploader
id="upload" id="upload"
class="ar-icon ar-icon__sm" class="ar-icon ar-icon__sm"
name="save" :record="record"
@click.native="upload"/> :options="uploaderOptions"/>
</div> </div>
<div class="ar-player-bar"> <div class="ar-player-bar">
@@ -120,32 +120,31 @@
</template> </template>
<script> <script>
import IconButton from './icon-button' import IconButton from './icon-button'
import LineControl from './line-control' import LineControl from './line-control'
import Uploader from './uploader'
import VolumeControl from './volume-control' import VolumeControl from './volume-control'
import { convertTimeMMSS } from '@/lib/utils.js' import { convertTimeMMSS } from '@/lib/utils'
export default { export default {
props: { props: {
src : { type: String }, src : { type: String },
uploadUrl : { type: String }, record : { type: Object },
record : { type: Object }, compact : { type: Boolean, default: true },
compact : { type: Boolean, default: true }, uploaderOptions : { type: Object, default: () => new Object }
startUpload : { type: Function },
successfulUpload : { type: Function },
failedUpload : { type: Function }
}, },
data () { data () {
return { return {
isPlaying: false, isPlaying : false,
duration: convertTimeMMSS(0), duration : convertTimeMMSS(0),
playedTime: convertTimeMMSS(0), playedTime : convertTimeMMSS(0),
progress: 0 progress : 0
} }
}, },
components: { components: {
IconButton, IconButton,
LineControl, LineControl,
Uploader,
VolumeControl VolumeControl
}, },
mounted: function() { mounted: function() {
@@ -161,24 +160,29 @@
}) })
this.player.addEventListener('timeupdate', this._onTimeUpdate) this.player.addEventListener('timeupdate', this._onTimeUpdate)
this.$eventBus.$on('remove-record', () => {
this._resetProgress()
})
}, },
computed: { computed: {
audioSource () {
let url = this.src || this.record.url
if (url) {
return url
} else {
this._resetProgress()
}
},
playBtnIcon () { playBtnIcon () {
return this.isPlaying ? 'pause' : 'play' return this.isPlaying ? 'pause' : 'play'
}, },
audioSource () {
return this.src || this.record.url
},
playerUniqId () { playerUniqId () {
return `audio-player${this._uid}` return `audio-player${this._uid}`
} }
}, },
methods: { methods: {
playback () { playback () {
if (!this.audioSource) {
return
}
if (this.isPlaying) { if (this.isPlaying) {
this.player.pause() this.player.pause()
} else { } else {
@@ -187,47 +191,27 @@
this.isPlaying = !this.isPlaying this.isPlaying = !this.isPlaying
}, },
upload () {
if (!this.audioSource) {
return
}
if (this.startUpload) {
this.startUpload()
}
this.$emit('on-start-upload')
let data = new FormData()
data.append('audio', this.record.blob, 'my-record')
this.$http.post(this.uploadUrl, data, {
headers: {'Content-Type': `multipart/form-data; boundary=${data._boundary}`}
}).then(resp => {
this.$emit('on-end-upload', 'success')
if (this.successfulUpload) {
this.successfulUpload(resp)
}
}).catch(error => {
this.$emit('on-end-upload', 'fail')
if (this.failedUpload) {
this.failedUpload(error)
}
})
},
download () { download () {
if (!this.audioSource) {
return
}
let link = document.createElement('a') let link = document.createElement('a')
link.href = this.record.url link.href = this.record.url
link.download = 'record.wav' link.download = 'record.wav'
link.click() link.click()
}, },
decorator (func) {
if (!this.audioSource) {
return
}
func()
},
_resetProgress () { _resetProgress () {
this.isPlaying = false if (this.isPlaying) {
this.progress = 0 this.player.pause()
}
this.duration = convertTimeMMSS(0)
this.playedTime = convertTimeMMSS(0)
this.progress = 0
this.isPlaying = false
}, },
_onTimeUpdate () { _onTimeUpdate () {
this.playedTime = convertTimeMMSS(this.player.currentTime) this.playedTime = convertTimeMMSS(this.player.currentTime)

View File

@@ -23,12 +23,14 @@
&__record { &__record {
width: 320px; width: 320px;
height: 45px;
padding: 0 10px; padding: 0 10px;
margin: 0 auto; margin: 0 auto;
line-height: 45px; line-height: 45px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
border-bottom: 1px solid #E8E8E8; border-bottom: 1px solid #E8E8E8;
position: relative;
&--selected { &--selected {
border: 1px solid #E8E8E8; border: 1px solid #E8E8E8;
@@ -105,7 +107,7 @@
@keyframes blink { @keyframes blink {
0% { opacity: .2; } 0% { opacity: .2; }
20% { opacity: 1; } 20% { opacity: 1; }
100% { opacity: .2; } 100% { opacity: .2; }
} }
} }
@@ -144,6 +146,20 @@
color: red; color: red;
} }
} }
&__rm {
cursor: pointer;
position: absolute;
width: 6px;
height: 6px;
padding: 6px;
line-height: 6px;
margin: auto;
left: 10px;
bottom: 0;
top: 0;
color: rgb(244, 120, 90);
}
} }
@import '../scss/icons'; @import '../scss/icons';
@@ -182,23 +198,20 @@
<div class="ar-records"> <div class="ar-records">
<div <div
class="ar-records__record" class="ar-records__record"
:class="{'ar-records__record--selected': idx === selectedRecord.idx}" :class="{'ar-records__record--selected': record.id === selected.id}"
:key="record.id"
v-for="(record, idx) in recordList" v-for="(record, idx) in recordList"
@click="selectRecord(idx, record)"> @click="selected = record">
<div
class="ar__rm"
v-if="record.id === selected.id"
@click="removeRecord(idx)">&times;</div>
<div class="ar__text">Record {{idx + 1}}</div> <div class="ar__text">Record {{idx + 1}}</div>
<div class="ar__text">{{record.duration}}</div> <div class="ar__text">{{record.duration}}</div>
</div> </div>
</div> </div>
<audio-player <audio-player :compact="compact" :record="selected" :uploader-options="uploaderOptions"/>
:compact="compact"
:record="selectedRecord"
:upload-url="uploadUrl"
:start-upload="startUpload"
:successful-upload="successfulUpload"
:failed-upload="failedUpload"
@start-upload="onStartUpload"
@end-upload="onEndUpload"/>
<div :class="uploadStatusClasses" v-if="uploadStatus">{{message}}</div> <div :class="uploadStatusClasses" v-if="uploadStatus">{{message}}</div>
</div> </div>
@@ -206,52 +219,76 @@
</template> </template>
<script> <script>
import AudioPlayer from './player.vue' import AudioPlayer from './player'
import IconButton from './icon-button.vue' import IconButton from './icon-button'
import Recorder from '@/lib/recorder.js' import Recorder from '@/lib/recorder'
import { convertTimeMMSS } from '@/lib/utils.js' import { convertTimeMMSS } from '@/lib/utils'
export default { export default {
props: { props: {
attempts : { type: Number }, attempts : { type: Number },
compact : { type: Boolean, default: false }, compact : { type: Boolean, default: false },
time : { type: Number }, time : { type: Number },
uploadUrl : { type: String },
attemptsLimit : { type: Function },
micFailed : { type: Function },
startRecord : { type: Function },
stopRecord : { type: Function },
attemptsLimit : { type: Function },
failedUpload : { type: Function }, failedUpload : { type: Function },
micFailed : { type: Function }, headers : { type: Object },
startRecord : { type: Function },
startUpload : { type: Function }, startUpload : { type: Function },
stopRecord : { type: Function },
successfulUpload : { type: Function }, successfulUpload : { type: Function },
uploadUrl : { type: String },
successfulUploadMsg : { type: String, default: 'Upload successful' }, successfulUploadMsg : { type: String, default: 'Upload successful' },
failedUploadMsg : { type: String, default: 'Upload fail' } failedUploadMsg : { type: String, default: 'Upload fail' }
}, },
data () { data () {
return { return {
isUploading: false, isUploading : false,
recorder: new Recorder({ recorder : new Recorder({
afterStop: () => { afterStop: () => {
this.recordList = this.recorder.recordList() this.recordList = this.recorder.recordList()
if (this.stopRecord) {
if (this.stopRecord) { this.stopRecord('stop record')
this.stopRecord('stop record') }
} },
}, attempts: this.attempts,
attempts: this.attempts, time: this.time
time: this.time }),
}), recordList : [],
recordList: [], selected : {},
selectedRecord: {}, uploadStatus : null,
uploadStatus: null uploaderOptions : {}
} }
}, },
components: { components: {
AudioPlayer, AudioPlayer,
IconButton IconButton
}, },
created () {
this.uploaderOptions = {
failedUpload : this.failedUpload,
headers : this.headers,
startUpload : this.startUpload,
successfulUpload : this.successfulUpload,
uploadUrl : this.uploadUrl
}
this.$eventBus.$on('start-upload', () => {
this.isUploading = true
})
this.$eventBus.$on('end-upload', (resp) => {
this.isUploading = false
this.uploadStatus = status
setTimeout(() => {this.uploadStatus = null}, 1500)
})
},
beforeDestroy () {
this.stopRecorder()
},
methods: { methods: {
toggleRecorder () { toggleRecorder () {
if (this.attempts && this.recorder.records.length >= this.attempts) { if (this.attempts && this.recorder.records.length >= this.attempts) {
@@ -277,16 +314,10 @@
this.recorder.stop() this.recorder.stop()
}, },
selectRecord (idx, record) { removeRecord (idx) {
this.selectedRecord = { idx: idx, url: record.url, blob: record.blob } this.recordList.splice(idx, 1)
}, this.$set(this.selected, 'url', null)
onStartUpload () { this.$eventBus.$emit('remove-record')
this.isUploading = true
},
onEndUpload (status) {
this.isUploading = false
this.uploadStatus = status
setTimeout(() => {this.uploadStatus = null}, 1500)
} }
}, },
computed: { computed: {

View File

@@ -0,0 +1,54 @@
<style lang="scss">
@import '../scss/icons';
</style>
<template>
<icon-button name="save" @click.native="upload"/>
</template>
<script>
import IconButton from './icon-button'
export default {
props: {
options : { type: Object },
record : { type: Object }
},
components: {
IconButton
},
methods: {
upload () {
if (!this.record.url) {
return
}
this.$eventBus.$emit('start-upload')
if (this.options.startUpload) {
this.options.startUpload()
}
let data = new FormData()
data.append('audio', this.record.blob, 'my-record')
let headers = Object.assign(this.options.headers, {})
headers['Content-Type'] = `multipart/form-data; boundary=${data._boundary}`
this.$http.post(this.options.uploadUrl, data, { headers: headers }).then(resp => {
this.$eventBus.$emit('end-upload', 'success')
if (this.options.successfulUpload) {
this.options.successfulUpload(resp)
}
}).catch(error => {
this.$eventBus.$emit('end-upload', 'fail')
if (this.options.failedUpload) {
this.options.failedUpload(error)
}
})
}
}
}
</script>

View File

@@ -37,8 +37,8 @@
</template> </template>
<script> <script>
import IconButton from './icon-button.vue' import IconButton from './icon-button'
import LineControl from './line-control.vue' import LineControl from './line-control'
export default { export default {
data () { data () {

View File

@@ -1,5 +1,5 @@
import AudioPlayer from './components/player.vue' import AudioPlayer from '@/components/player.vue'
import AudioRecorder from './components/recorder.vue' import AudioRecorder from '@/components/recorder.vue'
const components = { const components = {
AudioPlayer, AudioPlayer,
@@ -12,6 +12,8 @@ const components = {
this.installed = true this.installed = true
Vue.prototype.$eventBus = Vue.prototype.$eventBus || new Vue
Vue.component('audio-player', AudioPlayer) Vue.component('audio-player', AudioPlayer)
Vue.component('audio-recorder', AudioRecorder) Vue.component('audio-recorder', AudioRecorder)
} }

View File

@@ -23,7 +23,7 @@ export default class {
navigator.mediaDevices.getUserMedia({audio: true}) navigator.mediaDevices.getUserMedia({audio: true})
.then(this._micCaptured.bind(this)) .then(this._micCaptured.bind(this))
.catch(this._micError.bind(this)) .catch(this._micError.bind(this))
this.isPause = false this.isPause = false
this.isRecording = true this.isRecording = true
} }
@@ -34,26 +34,28 @@ export default class {
this.context.close() this.context.close()
let encoder = new WavEncoder({ let encoder = new WavEncoder({
bufferSize: this.bufferSize, bufferSize : this.bufferSize,
sampleRate: this.context.sampleRate, sampleRate : this.context.sampleRate,
samples: this.samples samples : this.samples
}) })
let audioBlob = encoder.getData() let audioBlob = encoder.getData()
let audioUrl = URL.createObjectURL(audioBlob) let audioUrl = URL.createObjectURL(audioBlob)
this.samples = [] this.samples = []
this.records.push({ this.records.push({
blob: audioBlob, id : Date.now(),
url: audioUrl, blob : audioBlob,
duration: convertTimeMMSS(this.duration) duration : convertTimeMMSS(this.duration),
url : audioUrl
}) })
this.isPause = false
this.isRecording = false
this._duration = 0 this._duration = 0
this.duration = 0 this.duration = 0
this.isPause = false
this.isRecording = false
if (this.afterStop) { if (this.afterStop) {
this.afterStop() this.afterStop()
@@ -79,11 +81,11 @@ export default class {
} }
_micCaptured (stream) { _micCaptured (stream) {
this.context = new(window.AudioContext || window.webkitAudioContext)() this.context = new(window.AudioContext || window.webkitAudioContext)()
this.input = this.context.createMediaStreamSource(stream) this.duration = this._duration
this.processor = this.context.createScriptProcessor(this.bufferSize, 1, 1) this.input = this.context.createMediaStreamSource(stream)
this.duration = this._duration this.processor = this.context.createScriptProcessor(this.bufferSize, 1, 1)
this.stream = stream this.stream = stream
this.processor.onaudioprocess = (ev) => { this.processor.onaudioprocess = (ev) => {
let sample = ev.inputBuffer.getChannelData(0) let sample = ev.inputBuffer.getChannelData(0)

View File

@@ -1,67 +1,70 @@
const webpack = require('webpack') const webpack = require('webpack')
const merge = require('webpack-merge') const merge = require('webpack-merge')
const env = `./webpack.${process.env.NODE_ENV === 'production' ? 'prod' : 'dev'}.js`
const path = require('path') const path = require('path')
module.exports = merge(require(env), { module.exports = (env, args) => {
module: { let conf = `./webpack.${env.NODE_ENV === 'production' ? 'prod' : 'dev'}.js`
rules: [
{ return merge(require(conf), {
test: /\.scss$/, module: {
use: [ rules: [
'vue-style-loader', {
'css-loader', test: /\.scss$/,
'sass-loader' use: [
], 'vue-style-loader',
}, 'css-loader',
{ 'sass-loader'
test: /\.vue$/, ],
loader: 'vue-loader', },
options: { {
loaders: { test: /\.vue$/,
'scss': [ loader: 'vue-loader',
'vue-style-loader', options: {
'css-loader', loaders: {
'sass-loader' 'scss': [
] 'vue-style-loader',
'css-loader',
'sass-loader'
]
}
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
} }
} }
}, ]
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': path.resolve(__dirname, 'src')
}, },
extensions: ['*', '.js', '.vue', '.json'] resolve: {
}, alias: {
plugins: [ 'vue$': 'vue/dist/vue.esm.js',
new webpack.DefinePlugin({ '@': path.resolve(__dirname, 'src')
'process.env': {
NODE_ENV: `"${process.env.NODE_ENV}"`
}, },
VERSION: JSON.stringify(require("./package.json").version) extensions: ['*', '.js', '.vue', '.json']
}), },
], plugins: [
devServer: { new webpack.DefinePlugin({
historyApiFallback: true, 'process.env': {
noInfo: true, NODE_ENV: `"${process.env.NODE_ENV}"`
overlay: true },
}, VERSION: JSON.stringify(require("./package.json").version)
performance: { }),
hints: false ],
} devServer: {
}) historyApiFallback: true,
noInfo: true,
overlay: true
},
performance: {
hints: false
}
})
}

View File

@@ -2,7 +2,6 @@ const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = { module.exports = {
devtool: '#eval-source-map',
entry: './demo/index.js', entry: './demo/index.js',
output: { output: {
path: path.resolve(__dirname, './demo') path: path.resolve(__dirname, './demo')

View File

@@ -1,9 +1,11 @@
const path = require('path') const path = require('path')
const webpack = require('webpack') const webpack = require('webpack')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = { module.exports = {
devtool: '#source-map', entry: {
entry: './src/index.js', main: './src/index.js'
},
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'vue-audio-recorder.min.js', filename: 'vue-audio-recorder.min.js',
@@ -12,18 +14,21 @@ module.exports = {
libraryExport: 'default', libraryExport: 'default',
umdNamedDefine: true umdNamedDefine: true
}, },
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
})
]
},
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: '"production"' NODE_ENV: '"production"'
} }
}), }),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: true
}
}),
new webpack.LoaderOptionsPlugin({ new webpack.LoaderOptionsPlugin({
minimize: false minimize: false
}) })