├── .eslintignore ├── .travis.yml ├── test ├── unit │ ├── .eslintrc │ ├── index.js │ ├── karma.conf.js │ └── specs │ │ └── VueAwesomeSwiper.spec.js └── e2e │ ├── custom-assertions │ └── elementCount.js │ ├── runner.js │ └── nightwatch.conf.js ├── examples ├── nuxt-ssr-example │ ├── nuxt-video-player-plugin.js │ ├── nuxt.config.js │ └── nuxt-ssr-example.vue ├── index.js ├── 05-video.vue ├── 04-video.vue ├── 03-video.vue ├── 02-video.vue └── 01-video.vue ├── .babelrc ├── config ├── test.conf.js ├── build.conf.js └── base.conf.js ├── .npmignore ├── CHANGELOG.md ├── .github └── ISSUE_TEMPLATE.md ├── bower.json ├── src ├── index.js ├── custom-theme.css ├── ssr.js └── player.vue ├── .gitignore ├── LICENSE ├── package.json ├── dist ├── vue-video-player.js └── ssr.js ├── README.md └── .eslintrc.js /.eslintignore: -------------------------------------------------------------------------------- 1 | config/*.js 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | after_success: 5 | - codeclimate-test-reporter < ./test/unit/coverage/lcov.info -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/nuxt-ssr-example/nuxt-video-player-plugin.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue' 3 | 4 | const VueVideoPlayer = require('vue-video-player/dist/ssr') 5 | Vue.use(VueVideoPlayer) 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": [ 4 | "transform-es2015-destructuring", 5 | "transform-object-rest-spread" 6 | ], 7 | "comments": false, 8 | "env": { 9 | "test": { 10 | "plugins": [ "istanbul" ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/nuxt-ssr-example/nuxt.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // some nuxt config... 3 | plugins: [ 4 | { src: '~plugins/nuxt-video-player-plugin.js', ssr: false } 5 | ], 6 | // some nuxt config... 7 | css: [ 8 | 'video.js/dist/video-js.css' 9 | ], 10 | // some nuxt config... 11 | } 12 | -------------------------------------------------------------------------------- /config/test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | const merge = require('webpack-merge') 4 | const baseConfig = require('./base.conf') 5 | 6 | module.exports = merge(baseConfig, { 7 | // use inline sourcemap for karma-sourcemap-loader 8 | devtool: '#inline-source-map' 9 | }) 10 | 11 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import example01 from './01-video.vue' 2 | import example02 from './02-video.vue' 3 | import example03 from './03-video.vue' 4 | import example04 from './04-video.vue' 5 | import example05 from './05-video.vue' 6 | 7 | export default { 8 | example01, 9 | example02, 10 | example03, 11 | example04, 12 | example05 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .DS_Store 3 | .babelrc 4 | .editorconfig 5 | .eslintrc.js 6 | .eslintignore 7 | .gitignore 8 | .travis.yml 9 | 10 | test/ 11 | test/unit/coverage 12 | test/e2e/reports 13 | 14 | gh-pages/ 15 | static/ 16 | build/ 17 | config/ 18 | node_modules 19 | node_modules/ 20 | npm-debug.log 21 | examples 22 | examples/ 23 | 24 | index.html 25 | config.js 26 | bower.json 27 | package-lock.json 28 | selenium-debug.log 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## CHANGELOG 3 | 4 | ### v5.0.2 5 | 6 | fix #142 custom event 7 | 8 | ### v5.0.1 9 | 10 | support dynamic change options 11 | 12 | ### v5.0.0 13 | 14 | #### use 15 | 1. add global default options 16 | 2. update the options assign logic 17 | 3. Update to [video.js6](https://github.com/videojs/video.js) 18 | 19 | #### project 20 | - add brower support 21 | - add test scripts 22 | - update babel and webpack configs 23 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | // Polyfill fn.bind() for PhantomJS 2 | /* eslint-disable no-extend-native */ 3 | Function.prototype.bind = require('function-bind') 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src/', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ### 中文用户注意: 3 | 4 | 1. 尽量用英文描述你的 issue 5 | 2. 不要把内容堆彻在标题上,逻辑清晰地写在内容区 6 | 3. 贴代码要提前格式化好,有颜色高亮那种,贴文本,不要贴图片 7 | 4. 提问题前,必须仔细阅读 REMADE.md + 在已关闭的问题中寻找与自身相关的问题,90% 的可能它已经被解决 8 | 5. **如果无法做到提一个合格、优秀的问题,则问题会被 close + block** 9 | 10 | ### BUG REPORT TEMPLATE 11 | 12 | #### Vue.js version and component version 13 | 14 | #### Reproduction Link 15 | - A minimal JSBin, JSFiddle, Codepen, or a GitHub repository that can reproduce the bug. 16 | - You could start with this template: https://jsfiddle.net/39epgLj0/ 17 | 18 | #### Steps to reproduce 19 | 20 | #### What is Expected? 21 | 22 | #### What is actually happening? -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-video-player", 3 | "description": "videojs component for Vue", 4 | "main": "./dist/vue-video-player.js", 5 | "author": { 6 | "name": "Surmon", 7 | "email": "surmon@foxmail.com", 8 | "url": "http://surmon.me" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "vue-video-player", 13 | "vue video player", 14 | "video player", 15 | "vue player", 16 | "vue video" 17 | ], 18 | "homepage": "https://surmon-china.github.io/vue-video-player", 19 | "moduleType": [], 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Vue-Video-Player ssr.js 4 | * Author: surmon@foxmail.com 5 | * Github: https://github.com/surmon-china/vue-video-player 6 | * Adapted from Videojs (https://github.com/videojs/video.js) 7 | */ 8 | 9 | import _videojs from 'video.js' 10 | import videoPlayer from './player.vue' 11 | 12 | const videojs = window.videojs || _videojs 13 | const install = function (Vue, config) { 14 | if (config) { 15 | if (config.options) { 16 | videoPlayer.props.globalOptions.default = () => config.options 17 | } 18 | if (config.events) { 19 | videoPlayer.props.globalEvents.default = () => config.events 20 | } 21 | } 22 | Vue.component(videoPlayer.name, videoPlayer) 23 | } 24 | 25 | const VueVideoPlayer = { videojs, videoPlayer, install } 26 | 27 | export default VueVideoPlayer 28 | export { videojs, videoPlayer, install } 29 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | // 2. run the nightwatch test suite against it 6 | // to run in additional browsers: 7 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 8 | // 2. add it to the --env flag below 9 | // For more information on Nightwatch's config file, see 10 | // http://nightwatchjs.org/guide#settings-file 11 | var spawn = require('cross-spawn') 12 | var runner = spawn( 13 | './node_modules/.bin/nightwatch', 14 | [ 15 | '--config', 'test/e2e/nightwatch.conf.js', 16 | '--env', 'chrome,firefox' 17 | ], 18 | { 19 | stdio: 'inherit' 20 | } 21 | ) 22 | 23 | runner.on('exit', function (code) { 24 | server.close() 25 | process.exit(code) 26 | }) 27 | 28 | runner.on('error', function (err) { 29 | server.close() 30 | throw err 31 | }) 32 | -------------------------------------------------------------------------------- /config/build.conf.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path') 3 | const merge = require('webpack-merge') 4 | const baseConfig = require('./base.conf') 5 | 6 | const resolve = dir => path.join(__dirname, '..', dir) 7 | 8 | module.exports = merge(baseConfig, { 9 | entry: { 10 | 'vue-video-player': './src/index.js' 11 | }, 12 | externals: { 13 | 'video.js': { 14 | root: 'videojs', 15 | commonjs: 'video.js', 16 | commonjs2: 'video.js', 17 | amd: 'videojs' 18 | }, 19 | 'object-assign': 'object-assign' 20 | }, 21 | output: { 22 | path: path.resolve(__dirname, '../dist'), 23 | publicPath: '/', 24 | filename: '[name].js', 25 | library: 'VueVideoPlayer', 26 | libraryTarget: 'umd' 27 | }, 28 | devtool: '#source-map', 29 | resolve: { 30 | extensions: ['.js', '.vue', '.json'], 31 | modules: [ 32 | resolve('src'), 33 | resolve('node_modules') 34 | ] 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | selenium-debug.log 6 | npm-debug.log* 7 | 8 | # System 9 | .DS_Store 10 | 11 | # Optional npm cache directory 12 | .npm 13 | 14 | # Optional REPL history 15 | .node_repl_history 16 | 17 | # Project 18 | .editorconfig 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Runtime data 30 | pids 31 | *.pid 32 | *.seed 33 | 34 | # Package 35 | package-lock.json 36 | 37 | # Test 38 | test/unit/coverage 39 | test/e2e/reports 40 | 41 | # Directory for instrumented libs generated by jscoverage/JSCover 42 | lib-cov 43 | 44 | # Coverage directory used by tools like istanbul 45 | coverage 46 | 47 | # Compiled binary addons (http://nodejs.org/api/addons.html) 48 | build/Release 49 | 50 | # Dependency directories 51 | node_modules 52 | node_modules/ 53 | jspm_packages 54 | -------------------------------------------------------------------------------- /config/base.conf.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path') 3 | const webpack = require('webpack') 4 | 5 | const resolve = dir => path.join(__dirname, '..', dir) 6 | 7 | const env = process.env.NODE_ENV === 'testing' 8 | ? { NODE_ENV: '"testing"' } 9 | : { NODE_ENV: '"production"' } 10 | 11 | module.exports = { 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.(js|vue)$/, 16 | loader: 'eslint-loader', 17 | enforce: 'pre', 18 | include: [resolve('src'), resolve('test')], 19 | options: { 20 | formatter: require('eslint-friendly-formatter') 21 | } 22 | }, 23 | { 24 | test: /\.vue$/, 25 | loader: 'vue-loader', 26 | options: {} 27 | }, 28 | { 29 | test: /\.js$/, 30 | loader: 'babel-loader', 31 | include: [resolve('src'), resolve('test')] 32 | } 33 | ] 34 | }, 35 | plugins: [ 36 | new webpack.DefinePlugin({ 37 | 'process.env': env 38 | }), 39 | new webpack.optimize.UglifyJsPlugin({ 40 | compress: { warnings: false } 41 | }) 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | // http://nightwatchjs.org/guide#settings-file 2 | module.exports = { 3 | "src_folders": ["test/e2e/specs"], 4 | "output_folder": "test/e2e/reports", 5 | "custom_assertions_path": ["test/e2e/custom-assertions"], 6 | 7 | "selenium": { 8 | "start_process": true, 9 | "server_path": "node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.0.jar", 10 | "host": "127.0.0.1", 11 | "port": 4444, 12 | "cli_args": { 13 | "webdriver.chrome.driver": require('chromedriver').path 14 | } 15 | }, 16 | 17 | "test_settings": { 18 | "default": { 19 | "selenium_port": 4444, 20 | "selenium_host": "localhost", 21 | "silent": true 22 | }, 23 | 24 | "chrome": { 25 | "desiredCapabilities": { 26 | "browserName": "chrome", 27 | "javascriptEnabled": true, 28 | "acceptSslCerts": true 29 | } 30 | }, 31 | 32 | "firefox": { 33 | "desiredCapabilities": { 34 | "browserName": "firefox", 35 | "javascriptEnabled": true, 36 | "acceptSslCerts": true 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Simon Babay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/05-video.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 44 | 45 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../config/test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | // 浏览器环境 15 | browsers: ['PhantomJS'], 16 | // 测试框架 17 | frameworks: ['mocha', 'sinon-chai'], 18 | // 输出报告 19 | reporters: ['spec', 'coverage'], 20 | // 在browsers里面运行,把需要测试的文件都 require 进来,在 browsers 里跑,使用 frameworks 测试js,通过 reporters 输出报告 21 | files: ['./index.js'], 22 | // 为入口文件制定预处理器,测试 index.js 之前用 webpack 和 sourcemap 处理一下 23 | preprocessors: { 24 | './index.js': ['webpack', 'sourcemap'] 25 | }, 26 | // 给webpack指定相关的配置文件 27 | webpack: webpackConfig, 28 | webpackMiddleware: { 29 | noInfo: true 30 | }, 31 | // 覆盖报告,coverage 是代码测试覆盖率的一个 reporter,也就是说告诉你项目的代码有多少测试了 32 | // 下面是 vue-cli 对这个的一个配置 33 | coverageReporter: { 34 | dir: './coverage', 35 | reporters: [ 36 | { type: 'lcov', subdir: '.' }, 37 | { type: 'text-summary' } 38 | ] 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /examples/04-video.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 70 | 71 | -------------------------------------------------------------------------------- /examples/03-video.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 73 | -------------------------------------------------------------------------------- /examples/02-video.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 82 | 83 | -------------------------------------------------------------------------------- /examples/nuxt-ssr-example/nuxt-ssr-example.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 86 | 87 | 98 | -------------------------------------------------------------------------------- /examples/01-video.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 135 | 136 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-video-player", 3 | "description": "video.js component for Vue", 4 | "version": "5.0.2", 5 | "license": "MIT", 6 | "private": false, 7 | "author": { 8 | "name": "Surmon", 9 | "email": "surmon@foxmail.com", 10 | "url": "https://surmon.me" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/surmon-china/vue-video-player/issues" 14 | }, 15 | "homepage": "https://github.com/surmon-china/vue-video-player#readme", 16 | "main": "dist/vue-video-player.js", 17 | "unpkg": "dist/vue-video-player.js", 18 | "jsnext:main": "dist/vue-video-player.js", 19 | "files": [ 20 | "dist", 21 | "src" 22 | ], 23 | "jspm": { 24 | "main": "dist/vue-video-player.js", 25 | "registry": "npm", 26 | "format": "esm" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/surmon-china/vue-video-player.git" 31 | }, 32 | "keywords": [ 33 | "vue-video-player", 34 | "vue video player", 35 | "video player", 36 | "vue player", 37 | "vue video" 38 | ], 39 | "scripts": { 40 | "build:spa": "cross-env NODE_ENV=production webpack --config config/build.conf.js", 41 | "build:ssr": "babel src/ssr.js --out-file dist/ssr.js", 42 | "build": "npm run build:spa && npm run build:ssr", 43 | "unit": "cross-env BABEL_ENV=test NODE_ENV=testing karma start test/unit/karma.conf.js --watch", 44 | "test": "cross-env BABEL_ENV=test NODE_ENV=testing karma start test/unit/karma.conf.js --single-run", 45 | "lint": "eslint --ext .js,.vue src test/unit/specs", 46 | "finish": "npm run lint && npm test && npm run build", 47 | "publish": "git push && git push --tags && npm publish" 48 | }, 49 | "dependencies": { 50 | "object-assign": "^4.1.1", 51 | "video.js": "^6.6.0", 52 | "videojs-contrib-hls": "^5.12.2", 53 | "videojs-flash": "^2.1.0", 54 | "videojs-hotkeys": "^0.2.20" 55 | }, 56 | "expDependencies": { 57 | "node-sass": "^4.7.2", 58 | "sass-loader": "^6.0.6", 59 | "vue-video-player": "^5.0.0" 60 | }, 61 | "devDependencies": { 62 | "autoprefixer": "^6.7.2", 63 | "babel-cli": "^6.23.0", 64 | "babel-core": "^6.24.1", 65 | "babel-eslint": "^7.1.1", 66 | "babel-helper-vue-jsx-merge-props": "^2.0.2", 67 | "babel-loader": "^6.2.10", 68 | "babel-plugin-istanbul": "^3.1.2", 69 | "babel-plugin-syntax-jsx": "^6.13.0", 70 | "babel-plugin-transform-es2015-destructuring": "^6.23.0", 71 | "babel-plugin-transform-export-extensions": "^6.8.0", 72 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 73 | "babel-plugin-transform-runtime": "^6.23.0", 74 | "babel-preset-es2015": "^6.24.1", 75 | "babel-preset-stage-2": "^6.22.0", 76 | "babel-register": "^6.0.0", 77 | "chai": "^3.5.0", 78 | "chalk": "^1.1.3", 79 | "connect-history-api-fallback": "^1.1.0", 80 | "copy-webpack-plugin": "^4.0.0", 81 | "cross-env": "^5.0.0", 82 | "cross-spawn": "^5.1.0", 83 | "css-loader": "^0.25.0", 84 | "eslint": "^3.14.1", 85 | "eslint-config-standard": "^6.1.0", 86 | "eslint-friendly-formatter": "^2.0.5", 87 | "eslint-loader": "^1.6.1", 88 | "eslint-plugin-html": "^2.0.0", 89 | "eslint-plugin-promise": "^3.4.0", 90 | "eslint-plugin-standard": "^2.0.1", 91 | "eventsource-polyfill": "^0.9.6", 92 | "express": "^4.13.3", 93 | "extract-text-webpack-plugin": "^2.0.0-rc.3", 94 | "file-loader": "^0.10.0", 95 | "friendly-errors-webpack-plugin": "^1.1.3", 96 | "function-bind": "^1.1.0", 97 | "html-loader": "^0.4.4", 98 | "html-webpack-plugin": "^2.28.0", 99 | "http-proxy-middleware": "^0.17.3", 100 | "inject-loader": "^2.0.1", 101 | "json-loader": "^0.5.4", 102 | "jstransformer-markdown-it": "^2.0.0", 103 | "karma": "^1.4.1", 104 | "karma-coverage": "^1.1.1", 105 | "karma-mocha": "^1.3.0", 106 | "karma-phantomjs-launcher": "^1.0.2", 107 | "karma-sinon-chai": "^1.2.4", 108 | "karma-sourcemap-loader": "^0.3.7", 109 | "karma-spec-reporter": "0.0.26", 110 | "karma-webpack": "^2.0.2", 111 | "lolex": "^1.5.2", 112 | "mocha": "^3.2.0", 113 | "opn": "^4.0.2", 114 | "optimize-css-assets-webpack-plugin": "^1.3.0", 115 | "ora": "^0.3.0", 116 | "phantomjs-prebuilt": "^2.1.3", 117 | "raw-loader": "^0.5.1", 118 | "semver": "^5.3.0", 119 | "shelljs": "^0.7.4", 120 | "sinon": "^2.1.0", 121 | "sinon-chai": "^2.8.0", 122 | "uglify-js": "^3.0.15", 123 | "url-loader": "^0.5.7", 124 | "vue": "^2.5.0", 125 | "vue-hot-reload-api": "^1.2.0", 126 | "vue-html-loader": "^1.0.0", 127 | "vue-loader": "^13.3.0", 128 | "vue-template-compiler": "^2.5.2", 129 | "vue-template-es2015-compiler": "^1.6.0", 130 | "webpack": "^2.2.1", 131 | "webpack-bundle-analyzer": "^2.2.1", 132 | "webpack-dev-middleware": "^1.10.0", 133 | "webpack-hot-middleware": "^2.16.1", 134 | "webpack-merge": "^2.6.1" 135 | }, 136 | "engines": { 137 | "node": ">= 4.0.0", 138 | "npm": ">= 3.0.0" 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /dist/vue-video-player.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("video.js")):"function"==typeof define&&define.amd?define(["videojs"],t):"object"==typeof exports?exports.VueVideoPlayer=t(require("video.js")):e.VueVideoPlayer=t(e.videojs)}(this,function(e){return function(e){function t(i){if(n[i])return n[i].exports;var r=n[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,i){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:i})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/",t(t.s=3)}([function(t,n){t.exports=e},function(e,t,n){"use strict";function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(t,"__esModule",{value:!0});var r=n(0),o=function(e){return e&&e.__esModule?e:{default:e}}(r),s=window.videojs||o.default;"function"!=typeof Object.assign&&Object.defineProperty(Object,"assign",{value:function(e,t){if(null==e)throw new TypeError("Cannot convert undefined or null to object");for(var n=Object(e),i=1;i .video-js { 2 | width: 100%; 3 | font-family: "PingFang SC","Helvetica Neue","Hiragino Sans GB","Segoe UI","Microsoft YaHei","微软雅黑",sans-serif; 4 | } 5 | 6 | .vjs-custom-skin > .video-js .vjs-menu-button-inline.vjs-slider-active,.vjs-custom-skin > .video-js .vjs-menu-button-inline:focus,.vjs-custom-skin > .video-js .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline { 7 | width: 10em 8 | } 9 | 10 | .vjs-custom-skin > .video-js .vjs-controls-disabled .vjs-big-play-button { 11 | display: none!important 12 | } 13 | 14 | .vjs-custom-skin > .video-js .vjs-control { 15 | width: 3em 16 | } 17 | 18 | .vjs-custom-skin > .video-js .vjs-control.vjs-live-control{ 19 | width: auto; 20 | padding-left: .5em; 21 | letter-spacing: .1em; 22 | } 23 | 24 | .vjs-custom-skin > .video-js .vjs-menu-button-inline:before { 25 | width: 1.5em 26 | } 27 | 28 | .vjs-menu-button-inline .vjs-menu { 29 | left: 3em 30 | } 31 | 32 | .vjs-paused.vjs-has-started.vjs-custom-skin > .video-js .vjs-big-play-button,.video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button { 33 | display: block 34 | } 35 | 36 | .vjs-custom-skin > .video-js .vjs-load-progress div,.vjs-seeking .vjs-big-play-button,.vjs-waiting .vjs-big-play-button { 37 | display: none!important 38 | } 39 | 40 | .vjs-custom-skin > .video-js .vjs-mouse-display:after,.vjs-custom-skin > .video-js .vjs-play-progress:after { 41 | padding: 0 .4em .3em 42 | } 43 | 44 | .video-js.vjs-ended .vjs-loading-spinner { 45 | display: none; 46 | } 47 | 48 | .video-js.vjs-ended .vjs-big-play-button { 49 | display: block !important; 50 | } 51 | 52 | .video-js.vjs-ended .vjs-big-play-button,.video-js.vjs-paused .vjs-big-play-button,.vjs-paused.vjs-has-started.vjs-custom-skin > .video-js .vjs-big-play-button { 53 | display: block 54 | } 55 | 56 | .vjs-custom-skin > .video-js .vjs-big-play-button { 57 | top: 50%; 58 | left: 50%; 59 | margin-left: -1.5em; 60 | margin-top: -1em 61 | } 62 | 63 | .vjs-custom-skin > .video-js .vjs-big-play-button { 64 | background-color: rgba(0,0,0,0.45); 65 | font-size: 3.5em; 66 | /*border-radius: 50%;*/ 67 | height: 2em !important; 68 | line-height: 2em !important; 69 | margin-top: -1em !important 70 | } 71 | 72 | .video-js:hover .vjs-big-play-button,.vjs-custom-skin > .video-js .vjs-big-play-button:focus,.vjs-custom-skin > .video-js .vjs-big-play-button:active { 73 | background-color: rgba(36,131,213,0.9) 74 | } 75 | 76 | .vjs-custom-skin > .video-js .vjs-loading-spinner { 77 | border-color: rgba(36,131,213,0.8) 78 | } 79 | 80 | .vjs-custom-skin > .video-js .vjs-control-bar2 { 81 | background-color: #000000 82 | } 83 | 84 | .vjs-custom-skin > .video-js .vjs-control-bar { 85 | /*background-color: rgba(0,0,0,0.3) !important;*/ 86 | color: #ffffff; 87 | font-size: 14px 88 | } 89 | 90 | .vjs-custom-skin > .video-js .vjs-play-progress,.vjs-custom-skin > .video-js .vjs-volume-level { 91 | background-color: #2483d5 92 | } 93 | 94 | .vjs-custom-skin > .video-js .vjs-play-progress:before { 95 | top: -0.3em; 96 | } 97 | 98 | .vjs-custom-skin > .video-js .vjs-progress-control:hover .vjs-progress-holder { 99 | font-size: 1.3em; 100 | } 101 | 102 | .vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu { 103 | left: 0em; 104 | } 105 | 106 | .vjs-custom-skin > .video-js .vjs-menu li { 107 | padding: 0; 108 | line-height: 2em; 109 | font-size: 1.1em; 110 | font-family: "PingFang SC","Helvetica Neue","Hiragino Sans GB","Segoe UI","Microsoft YaHei","微软雅黑",sans-serif; 111 | } 112 | 113 | .vjs-custom-skin > .video-js .vjs-time-tooltip, 114 | .vjs-custom-skin > .video-js .vjs-mouse-display:after, 115 | .vjs-custom-skin > .video-js .vjs-play-progress:after { 116 | border-radius: 0; 117 | font-size: 1em; 118 | padding: 0; 119 | width: 3em; 120 | height: 1.5em; 121 | line-height: 1.5em; 122 | top: -3em; 123 | } 124 | 125 | .vjs-custom-skin > .video-js .vjs-menu-button-popup .vjs-menu { 126 | width: 5em; 127 | left: -1em; 128 | } 129 | 130 | .vjs-custom-skin > .video-js .vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu { 131 | left: 0; 132 | } 133 | 134 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-resolution-button .vjs-menu { 135 | /*order: 4;*/ 136 | } 137 | 138 | /*排序顺序*/ 139 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-play-control { 140 | order: 0; 141 | } 142 | 143 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-time-control { 144 | min-width: 1em; 145 | padding: 0; 146 | margin: 0 .1em; 147 | text-align: center; 148 | display: block; 149 | order: 1; 150 | } 151 | 152 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-playback-rate .vjs-playback-rate-value{ 153 | font-size: 1.2em; 154 | line-height: 2.4; 155 | } 156 | 157 | .vjs-custom-skin > .video-js .vjs-progress-control.vjs-control { 158 | order: 2; 159 | } 160 | 161 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-volume-menu-button { 162 | order: 3; 163 | } 164 | 165 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-resolution-button { 166 | order: 4; 167 | } 168 | 169 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-resolution-button .vjs-resolution-button-label { 170 | display: block; 171 | line-height: 3em; 172 | } 173 | 174 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-playback-rate { 175 | order: 5; 176 | } 177 | 178 | .vjs-custom-skin > .video-js .vjs-control-bar .vjs-fullscreen-control { 179 | order: 6; 180 | } 181 | -------------------------------------------------------------------------------- /src/ssr.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * VueVideoPlayer ssr.js 4 | * Author: surmon@foxmail.com 5 | * Github: https://github.com/surmon-china/vue-video-player 6 | */ 7 | 8 | // Require sources 9 | import videojs from 'video.js' 10 | import objectAssign from 'object-assign' 11 | 12 | // as of videojs 6.6.0 13 | const DEFAULT_EVENTS = [ 14 | 'loadeddata', 15 | 'canplay', 16 | 'canplaythrough', 17 | 'play', 18 | 'pause', 19 | 'waiting', 20 | 'playing', 21 | 'ended', 22 | 'error' 23 | ] 24 | 25 | const videoPlayerDirective = globalOptions => { 26 | 27 | // globalOptions 28 | globalOptions.events = globalOptions.events || [] 29 | globalOptions.options = globalOptions.options || {} 30 | 31 | // Get videojs instace name in directive 32 | const getInstanceName = (el, binding, vnode) => { 33 | let instanceName = null 34 | if (binding.arg) { 35 | instanceName = binding.arg 36 | } else if (vnode.data.attrs && (vnode.data.attrs.instanceName || vnode.data.attrs['instance-name'])) { 37 | instanceName = (vnode.data.attrs.instanceName || vnode.data.attrs['instance-name']) 38 | } else if (el.id) { 39 | instanceName = el.id 40 | } 41 | return instanceName || 'player' 42 | } 43 | 44 | // dom 45 | const repairDom = el => { 46 | if (!el.children.length) { 47 | const video = document.createElement('video') 48 | video.className = 'video-js' 49 | el.appendChild(video) 50 | } 51 | } 52 | 53 | // init 54 | const initPlayer = (el, binding, vnode) => { 55 | 56 | const self = vnode.context 57 | const attrs = vnode.data.attrs || {} 58 | const options = binding.value || {} 59 | const instanceName = getInstanceName(el, binding, vnode) 60 | const customEventName = attrs.customEventName || 'statechanged' 61 | let player = self[instanceName] 62 | 63 | // options 64 | const componentEvents = attrs.events || [] 65 | const playsinline = attrs.playsinline || false 66 | 67 | // ios fullscreen 68 | if (playsinline) { 69 | el.children[0].setAttribute('playsinline', playsinline) 70 | el.children[0].setAttribute('webkit-playsinline', playsinline) 71 | el.children[0].setAttribute('x5-playsinline', playsinline) 72 | el.children[0].setAttribute('x5-video-player-type', 'h5') 73 | el.children[0].setAttribute('x5-video-player-fullscreen', false) 74 | } 75 | 76 | // cross origin 77 | if (attrs.crossOrigin) { 78 | el.children[0].crossOrigin = attrs.crossOrigin 79 | el.children[0].setAttribute('crossOrigin', attrs.crossOrigin) 80 | } 81 | 82 | // initialize 83 | if (!player) { 84 | 85 | // videoOptions 86 | const videoOptions = objectAssign({}, { 87 | controls: true, 88 | controlBar: { 89 | remainingTimeDisplay: false, 90 | playToggle: {}, 91 | progressControl: {}, 92 | fullscreenToggle: {}, 93 | volumeMenuButton: { 94 | inline: false, 95 | vertical: true 96 | } 97 | }, 98 | techOrder: ['html5'], 99 | plugins: {} 100 | }, globalOptions.options, options) 101 | 102 | // plugins 103 | if (videoOptions.plugins) { 104 | delete videoOptions.plugins.__ob__ 105 | } 106 | 107 | // console.log('videoOptions', videoOptions) 108 | 109 | // eventEmit 110 | const eventEmit = (vnode, name, data) => { 111 | const handlers = (vnode.data && vnode.data.on) || 112 | (vnode.componentOptions && vnode.componentOptions.listeners) 113 | if (handlers && handlers[name]) handlers[name].fns(data) 114 | } 115 | 116 | // emit event 117 | const emitPlayerState = (event, value) => { 118 | if (event) { 119 | eventEmit(vnode, event, player) 120 | } 121 | if (value) { 122 | eventEmit(vnode, customEventName, { [event]: value }) 123 | } 124 | } 125 | 126 | // instance 127 | player = self[instanceName] = videojs(el.children[0], videoOptions, function() { 128 | 129 | // events 130 | const events = DEFAULT_EVENTS.concat(componentEvents).concat(globalOptions.events) 131 | 132 | // watch events 133 | const onEdEvents = {} 134 | for (let i = 0; i < events.length; i++) { 135 | if (typeof events[i] === 'string' && onEdEvents[events[i]] === undefined) { 136 | (event => { 137 | onEdEvents[event] = null 138 | this.on(event, () => { 139 | emitPlayerState(event, true) 140 | }) 141 | })(events[i]) 142 | } 143 | } 144 | 145 | // watch timeupdate 146 | this.on('timeupdate', function() { 147 | emitPlayerState('timeupdate', this.currentTime()) 148 | }) 149 | 150 | // player readied 151 | emitPlayerState('ready') 152 | }) 153 | } 154 | } 155 | 156 | // dispose 157 | const disposePlayer = (el, binding, vnode) => { 158 | const self = vnode.context 159 | const instanceName = getInstanceName(el, binding, vnode) 160 | const player = self[instanceName] 161 | if (player && player.dispose) { 162 | if (player.techName_ !== 'Flash') { 163 | player.pause && player.pause() 164 | } 165 | player.dispose() 166 | repairDom(el) 167 | self[instanceName] = null 168 | delete self[instanceName] 169 | } 170 | } 171 | 172 | return { 173 | inserted: initPlayer, 174 | 175 | // Bind directive 176 | bind(el, binding, vnode) { 177 | repairDom(el) 178 | }, 179 | 180 | // Parse text model change 181 | update(el, binding, vnode) { 182 | const options = binding.value || {} 183 | disposePlayer(el, binding, vnode) 184 | if (options && options.sources && options.sources.length) { 185 | initPlayer(el, binding, vnode) 186 | } 187 | }, 188 | 189 | // Destroy this directive 190 | unbind: disposePlayer 191 | } 192 | } 193 | 194 | // videoPlayer 195 | const videoPlayer = videoPlayerDirective({}) 196 | 197 | // Global quill default options 198 | const install = function (Vue, globalOptions = { 199 | options: {}, 200 | events: [] 201 | }) { 202 | 203 | // Mount quill directive for Vue global 204 | Vue.directive('video-player', videoPlayerDirective(globalOptions)) 205 | } 206 | 207 | const VueVideoPlayer = { videojs, videoPlayer, install } 208 | 209 | export default VueVideoPlayer 210 | export { videojs, videoPlayer, install } 211 | -------------------------------------------------------------------------------- /dist/ssr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.install = exports.videoPlayer = exports.videojs = undefined; 7 | 8 | var _video = require('video.js'); 9 | 10 | var _video2 = _interopRequireDefault(_video); 11 | 12 | var _objectAssign = require('object-assign'); 13 | 14 | var _objectAssign2 = _interopRequireDefault(_objectAssign); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 19 | 20 | var DEFAULT_EVENTS = ['loadeddata', 'canplay', 'canplaythrough', 'play', 'pause', 'waiting', 'playing', 'ended', 'error']; 21 | 22 | var videoPlayerDirective = function videoPlayerDirective(globalOptions) { 23 | globalOptions.events = globalOptions.events || []; 24 | globalOptions.options = globalOptions.options || {}; 25 | 26 | var getInstanceName = function getInstanceName(el, binding, vnode) { 27 | var instanceName = null; 28 | if (binding.arg) { 29 | instanceName = binding.arg; 30 | } else if (vnode.data.attrs && (vnode.data.attrs.instanceName || vnode.data.attrs['instance-name'])) { 31 | instanceName = vnode.data.attrs.instanceName || vnode.data.attrs['instance-name']; 32 | } else if (el.id) { 33 | instanceName = el.id; 34 | } 35 | return instanceName || 'player'; 36 | }; 37 | 38 | var repairDom = function repairDom(el) { 39 | if (!el.children.length) { 40 | var video = document.createElement('video'); 41 | video.className = 'video-js'; 42 | el.appendChild(video); 43 | } 44 | }; 45 | 46 | var initPlayer = function initPlayer(el, binding, vnode) { 47 | 48 | var self = vnode.context; 49 | var attrs = vnode.data.attrs || {}; 50 | var options = binding.value || {}; 51 | var instanceName = getInstanceName(el, binding, vnode); 52 | var customEventName = attrs.customEventName || 'statechanged'; 53 | var player = self[instanceName]; 54 | 55 | var componentEvents = attrs.events || []; 56 | var playsinline = attrs.playsinline || false; 57 | 58 | if (playsinline) { 59 | el.children[0].setAttribute('playsinline', playsinline); 60 | el.children[0].setAttribute('webkit-playsinline', playsinline); 61 | el.children[0].setAttribute('x5-playsinline', playsinline); 62 | el.children[0].setAttribute('x5-video-player-type', 'h5'); 63 | el.children[0].setAttribute('x5-video-player-fullscreen', false); 64 | } 65 | 66 | if (attrs.crossOrigin) { 67 | el.children[0].crossOrigin = attrs.crossOrigin; 68 | el.children[0].setAttribute('crossOrigin', attrs.crossOrigin); 69 | } 70 | 71 | if (!player) { 72 | var videoOptions = (0, _objectAssign2.default)({}, { 73 | controls: true, 74 | controlBar: { 75 | remainingTimeDisplay: false, 76 | playToggle: {}, 77 | progressControl: {}, 78 | fullscreenToggle: {}, 79 | volumeMenuButton: { 80 | inline: false, 81 | vertical: true 82 | } 83 | }, 84 | techOrder: ['html5'], 85 | plugins: {} 86 | }, globalOptions.options, options); 87 | 88 | if (videoOptions.plugins) { 89 | delete videoOptions.plugins.__ob__; 90 | } 91 | 92 | var eventEmit = function eventEmit(vnode, name, data) { 93 | var handlers = vnode.data && vnode.data.on || vnode.componentOptions && vnode.componentOptions.listeners; 94 | if (handlers && handlers[name]) handlers[name].fns(data); 95 | }; 96 | 97 | var emitPlayerState = function emitPlayerState(event, value) { 98 | if (event) { 99 | eventEmit(vnode, event, player); 100 | } 101 | if (value) { 102 | eventEmit(vnode, customEventName, _defineProperty({}, event, value)); 103 | } 104 | }; 105 | 106 | player = self[instanceName] = (0, _video2.default)(el.children[0], videoOptions, function () { 107 | var _this = this; 108 | 109 | var events = DEFAULT_EVENTS.concat(componentEvents).concat(globalOptions.events); 110 | 111 | var onEdEvents = {}; 112 | for (var i = 0; i < events.length; i++) { 113 | if (typeof events[i] === 'string' && onEdEvents[events[i]] === undefined) { 114 | (function (event) { 115 | onEdEvents[event] = null; 116 | _this.on(event, function () { 117 | emitPlayerState(event, true); 118 | }); 119 | })(events[i]); 120 | } 121 | } 122 | 123 | this.on('timeupdate', function () { 124 | emitPlayerState('timeupdate', this.currentTime()); 125 | }); 126 | 127 | emitPlayerState('ready'); 128 | }); 129 | } 130 | }; 131 | 132 | var disposePlayer = function disposePlayer(el, binding, vnode) { 133 | var self = vnode.context; 134 | var instanceName = getInstanceName(el, binding, vnode); 135 | var player = self[instanceName]; 136 | if (player && player.dispose) { 137 | if (player.techName_ !== 'Flash') { 138 | player.pause && player.pause(); 139 | } 140 | player.dispose(); 141 | repairDom(el); 142 | self[instanceName] = null; 143 | delete self[instanceName]; 144 | } 145 | }; 146 | 147 | return { 148 | inserted: initPlayer, 149 | 150 | bind: function bind(el, binding, vnode) { 151 | repairDom(el); 152 | }, 153 | update: function update(el, binding, vnode) { 154 | var options = binding.value || {}; 155 | disposePlayer(el, binding, vnode); 156 | if (options && options.sources && options.sources.length) { 157 | initPlayer(el, binding, vnode); 158 | } 159 | }, 160 | 161 | unbind: disposePlayer 162 | }; 163 | }; 164 | 165 | var videoPlayer = videoPlayerDirective({}); 166 | 167 | var install = function install(Vue) { 168 | var globalOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { 169 | options: {}, 170 | events: [] 171 | }; 172 | 173 | Vue.directive('video-player', videoPlayerDirective(globalOptions)); 174 | }; 175 | 176 | var VueVideoPlayer = { videojs: _video2.default, videoPlayer: videoPlayer, install: install }; 177 | 178 | exports.default = VueVideoPlayer; 179 | exports.videojs = _video2.default; 180 | exports.videoPlayer = videoPlayer; 181 | exports.install = install; 182 | -------------------------------------------------------------------------------- /src/player.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub stars](https://img.shields.io/github/stars/surmon-china/vue-video-player.svg?style=flat-square)](https://github.com/surmon-china/vue-video-player/stargazers) 2 | [![Build Status](https://travis-ci.org/surmon-china/vue-video-player.svg?branch=master)](https://travis-ci.org/surmon-china/vue-video-player) 3 | [![GitHub issues](https://img.shields.io/github/issues/surmon-china/vue-video-player.svg?style=flat-square)](https://github.com/surmon-china/vue-video-player/issues) 4 | [![GitHub forks](https://img.shields.io/github/forks/surmon-china/vue-video-player.svg?style=flat-square)](https://github.com/surmon-china/vue-video-player/network) 5 | [![GitHub last commit](https://img.shields.io/github/last-commit/google/skia.svg?style=flat-square)](https://github.com/surmon-china/vue-video-player) 6 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?style=flat-square)](https://github.com/surmon-china/vue-video-player) 7 | [![Twitter](https://img.shields.io/twitter/url/https/github.com/surmon-china/vue-video-player.svg?style=flat-square)](https://twitter.com/intent/tweet?url=https://github.com/surmon-china/vue-video-player) 8 | 9 | [![NPM](https://nodei.co/npm/vue-video-player.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/vue-video-player/) 10 | [![NPM](https://nodei.co/npm-dl/vue-video-player.png?months=9&height=3)](https://nodei.co/npm/vue-video-player/) 11 | 12 | 13 | # Vue-Video-Player 14 | 15 | [video.js](https://github.com/videojs/video.js) player component for Vue. 16 | 17 | 适用于 Vue 的 [video.js](https://github.com/videojs/video.js) 播放器组件。 18 | 19 | 20 | # Example 21 | 22 | [Demo Page](https://surmon-china.github.io/vue-video-player) 23 | 24 | [CDN Example](https://jsfiddle.net/u69gnx90/) 25 | 26 | [nuxt.js/ssr example code](https://github.com/surmon-china/vue-video-player/blob/master/examples/nuxt-ssr-example) 27 | 28 | [More Example Code](https://github.com/surmon-china/vue-video-player/tree/master/examples) 29 | 30 | 31 | # Install 32 | 33 | #### CDN 34 | 35 | ``` html 36 | 37 | 38 | 39 | 40 | 43 | ``` 44 | 45 | #### NPM 46 | 47 | ``` bash 48 | npm install vue-video-player --save 49 | ``` 50 | 51 | ### Mount 52 | 53 | #### mount with global 54 | 55 | ``` javascript 56 | import Vue from 'vue' 57 | import VueVideoPlayer from 'vue-video-player' 58 | 59 | // require videojs style 60 | import 'video.js/dist/video-js.css' 61 | // import 'vue-video-player/src/custom-theme.css' 62 | 63 | Vue.use(VueVideoPlayer, /* { 64 | options: global default options, 65 | events: global videojs events 66 | } */) 67 | ``` 68 | 69 | #### mount with component 70 | 71 | ```javascript 72 | // require styles 73 | import 'video.js/dist/video-js.css' 74 | 75 | import { videoPlayer } from 'vue-video-player' 76 | 77 | export default { 78 | components: { 79 | videoPlayer 80 | } 81 | } 82 | ``` 83 | 84 | #### mount with ssr 85 | 86 | ```javascript 87 | // If used in nuxt.js/ssr, you should keep it only in browser build environment 88 | if (process.browser) { 89 | const VueVideoPlayer = require('vue-video-player/dist/ssr') 90 | Vue.use(VueVideoPlayer) 91 | } 92 | ``` 93 | 94 | #### videojs extend 95 | 96 | ```javascript 97 | import videojs from 'video.js' 98 | 99 | // videojs plugin 100 | const Plugin = videojs.getPlugin('plugin') 101 | class ExamplePlugin extends Plugin { 102 | // something... 103 | } 104 | videojs.registerPlugin('examplePlugin', ExamplePlugin) 105 | 106 | // videojs language 107 | videojs.addLanguage('es', { 108 | Pause: 'Pausa', 109 | // something... 110 | }) 111 | 112 | // more videojs api... 113 | 114 | // vue component... 115 | ``` 116 | 117 | 118 | ### Difference(使用方法的异同) 119 | 120 | **SSR and the only difference in the use of the SPA:** 121 | - SPA worked by the `component`, find videojs instance by `ref attribute`. 122 | - SSR worked by the `directive`, find videojs instance by `directive arg`. 123 | - Other configurations, events are the same. 124 | 125 | 126 | ### SPA 127 | 128 | ``` vue 129 | 150 | 151 | 202 | ``` 203 | 204 | 205 | ### SSR 206 | 207 | ``` vue 208 | 209 | 218 | 219 | 228 | ``` 229 | 230 | 231 | # Issues 232 | 233 | [videojs-contrib-hls - e is not defined](https://github.com/surmon-china/vue-video-player/issues/90) 234 | 235 | 236 | # API 237 | - component api: 238 | * `events` : `[ Array, default: [] ]` : custom videojs event to component 239 | * `playsinline` : `[ Boolean, default: false ]` : set player not full-screen in mobile device 240 | * `crossOrigin` : `[ String, default: '' ]` : set crossOrigin to video 241 | * `customEventName` : `[ String, default: 'statechanged' ]` : custom the state change event name 242 | 243 | - video.js api 244 | * [video.js options](http://docs.videojs.com/tutorial-options.html) 245 | * [video.js docs](http://docs.videojs.com/) 246 | 247 | 248 | # videojs plugins 249 | 250 | - [videojs-resolution-switcher](https://github.com/kmoskwiak/videojs-resolution-switcher) 251 | - [videojs-contrib-hls](https://github.com/videojs/videojs-contrib-hls) 252 | - [videojs-youtube](https://github.com/videojs/videojs-youtube) 253 | - [videojs-vimeo](https://github.com/videojs/videojs-vimeo) 254 | - [videojs-hotkeys](https://github.com/ctd1500/videojs-hotkeys) 255 | - [videojs-flash](https://github.com/videojs/videojs-flash) 256 | - [videojs-contrib-ads](https://github.com/videojs/videojs-contrib-ads) 257 | - [more plugins...](https://github.com/search?o=desc&q=videojs+plugin&s=stars&type=Repositories&utf8=%E2%9C%93) 258 | 259 | 260 | # Author 261 | [Surmon](https://surmon.me) 262 | -------------------------------------------------------------------------------- /test/unit/specs/VueAwesomeSwiper.spec.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from 'vue/dist/vue.js' 3 | import Swiper from 'swiper/dist/js/swiper.js' 4 | import VueAwesomeSwiperSSR from '../../../src/ssr.js' 5 | import VueAwesomeSwiper, { swiper, swiperSlide, install } from '../../../src/index.js' 6 | 7 | // console.log('--------VueAwesomeSwiper', VueAwesomeSwiper) 8 | // console.log('--------VueAwesomeSwiperSSR', VueAwesomeSwiperSSR) 9 | 10 | describe('vue-awesome-swiper', () => { 11 | 12 | Vue.use(VueAwesomeSwiper) 13 | Vue.use(VueAwesomeSwiperSSR) 14 | 15 | // 测试解构是否成功 16 | it('can get the object in es module', () => { 17 | expect(typeof install).to.deep.equal('function') 18 | expect(swiper.name).to.deep.equal('swiper') 19 | expect(swiperSlide.name).to.deep.equal('swiper-slide') 20 | expect(typeof swiperSlide.methods.update).to.deep.equal('function') 21 | expect(typeof swiper.methods.update).to.deep.equal('function') 22 | expect(typeof VueAwesomeSwiperSSR.swiper).to.deep.equal('object') 23 | expect(typeof VueAwesomeSwiperSSR.install).to.deep.equal('function') 24 | }) 25 | 26 | // 全局安装 27 | describe('Global install spa:component', () => { 28 | it(' - should can get the swiper element', () => { 29 | const vm = new Vue({ 30 | template: ` 31 | Slide 1 32 | Slide 2 33 | Slide 3 34 | Slide 4 35 | Slide 5 36 | Slide 6 37 | Slide 7 38 | Slide 8 39 | Slide 9 40 | Slide 10 41 | ` 42 | }).$mount() 43 | expect(vm.$el.innerText).to.deep.equal(' Slide 1 Slide 2 Slide 3 Slide 4 Slide 5 Slide 6 Slide 7 Slide 8 Slide 9 Slide 10 ') 44 | expect(vm.$el.className).to.deep.equal('swiper-container swiper-container-horizontal') 45 | expect(vm.$el.children[0].className).to.deep.equal('swiper-wrapper') 46 | expect(vm.$el.children[0].children.length).to.deep.equal(10) 47 | }) 48 | }) 49 | 50 | // 全局配置测试 51 | describe('Get instance by attr ref and set component options', () => { 52 | it(' - should get the swiper instance and component options', done => { 53 | const vm = new Vue({ 54 | template: ` 55 | Slide 1 56 | Slide 2 57 | Slide 3 58 | Slide 4 59 | Slide 5 60 | Slide 6 61 | Slide 7 62 | Slide 8 63 | Slide 9 64 | Slide 10 65 |
66 |
67 |
`, 68 | data() { 69 | return { 70 | swiperOption: { 71 | navigation: { 72 | nextEl: '.swiper-button-next', 73 | prevEl: '.swiper-button-prev' 74 | } 75 | } 76 | } 77 | }, 78 | computed: { 79 | swiperComponent() { 80 | return this.$refs.mySwiper 81 | }, 82 | swiper() { 83 | return this.swiperComponent.swiper 84 | } 85 | } 86 | }).$mount() 87 | Vue.nextTick(() => { 88 | expect(vm.swiper instanceof Swiper).to.equal(true) 89 | expect(vm.swiperComponent.options.navigation.nextEl).to.deep.equal('.swiper-button-next') 90 | expect(vm.swiper.el.children[1].className).to.equal('swiper-button-prev') 91 | expect(vm.swiper.el.children[1].outerHTML).to.equal('
') 92 | expect(typeof vm.swiper.slideTo).to.equal('function') 93 | done() 94 | }) 95 | }) 96 | }) 97 | 98 | // 广播事件 99 | describe('Component emit event and data binding by evennt', () => { 100 | it(' - should capture event after the swiper emit event', done => { 101 | const eventLogs = [] 102 | const vm = new Vue({ 103 | template: `
104 | 105 | Slide 1 106 | Slide 2 107 | Slide 3 108 | Slide 4 109 | Slide 5 110 | Slide 6 111 | Slide 7 112 | Slide 8 113 | Slide 9 114 | Slide 10 115 |
116 |
117 |
118 |
119 |
120 | `, 121 | data() { 122 | return { 123 | swiperOption: { 124 | navigation: { 125 | nextEl: '.swiper-button-next', 126 | prevEl: '.swiper-button-prev' 127 | }, 128 | on: { 129 | init() { 130 | eventLogs.push('init') 131 | }, 132 | slideChange() { 133 | eventLogs.push('slideChange') 134 | }, 135 | } 136 | } 137 | } 138 | }, 139 | mounted() { 140 | eventLogs.push('mounted') 141 | } 142 | }).$mount() 143 | // console.log('----------', eventLogs) 144 | expect(eventLogs[0]).to.deep.equal('init') 145 | expect(eventLogs[1]).to.deep.equal('mounted') 146 | done() 147 | }) 148 | }) 149 | 150 | // 局部安装 151 | describe('Local install component', () => { 152 | it(' - should work', done => { 153 | const eventLogs = [] 154 | const vm = new Vue({ 155 | template: `
156 | 157 | Slide 1 158 | Slide 2 159 | Slide 3 160 | Slide 4 161 | Slide 5 162 | Slide 6 163 | Slide 7 164 | Slide 8 165 | Slide 9 166 | Slide 10 167 |
168 |
169 |
170 |
171 | `, 172 | components: { 173 | 'LocalSwiper': VueAwesomeSwiper.swiper, 174 | 'LocalSlide': VueAwesomeSwiper.swiperSlide, 175 | }, 176 | data: { 177 | swiperOption: { 178 | navigation: { 179 | nextEl: '.swiper-button-next', 180 | prevEl: '.swiper-button-prev' 181 | }, 182 | on: { 183 | init() { 184 | eventLogs.push('init') 185 | } 186 | } 187 | } 188 | }, 189 | computed: { 190 | swiperComponent() { 191 | return this.$refs.localSwiper 192 | }, 193 | swiper() { 194 | return this.swiperComponent.swiper 195 | } 196 | }, 197 | mounted() { 198 | eventLogs.push('mounted') 199 | } 200 | }).$mount() 201 | Vue.nextTick(() => { 202 | // console.log('----------', eventLogs) 203 | expect(eventLogs[0]).to.deep.equal('init') 204 | expect(eventLogs[1]).to.deep.equal('mounted') 205 | expect(vm.swiper instanceof Swiper).to.equal(true) 206 | expect(vm.swiperComponent.options.navigation.nextEl).to.deep.equal('.swiper-button-next') 207 | expect(vm.swiper.el.children[1].className).to.equal('swiper-button-prev') 208 | expect(vm.swiper.el.children[1].outerHTML).to.equal('
') 209 | expect(typeof vm.swiper.slideTo).to.equal('function') 210 | done() 211 | }) 212 | }) 213 | }) 214 | 215 | // SSR 全局安装测试 216 | describe('Global install ssr:directive', () => { 217 | it(' - should get swiper instance and capture event', done => { 218 | const eventLogs = [] 219 | const vm = new Vue({ 220 | template: `
221 |
222 |
223 |
224 | {{ slide }} 225 |
226 |
227 |
228 |
229 |
230 | `, 231 | data: { 232 | slides: [1, 2, 3], 233 | swiperOption: { 234 | on: { 235 | init() { 236 | eventLogs.push('ssr/init') 237 | } 238 | } 239 | } 240 | }, 241 | mounted() { 242 | eventLogs.push('ssr/mounted') 243 | } 244 | }).$mount() 245 | expect(eventLogs[0]).to.deep.equal('ssr/init') 246 | expect(eventLogs[1]).to.deep.equal('ssr/mounted') 247 | Vue.nextTick(() => { 248 | expect(vm.mySwiper instanceof Swiper).to.equal(true) 249 | expect(vm.mySwiper.el.children[0].className).to.equal('swiper-wrapper') 250 | expect(vm.mySwiper.el.children[1].className).to.equal('swiper-pagination swiper-pagination-bullets') 251 | expect(vm.mySwiper.el.children[1].outerHTML).to.equal('
') 252 | expect(typeof vm.mySwiper.slideTo).to.equal('function') 253 | expect(vm.mySwiper.el.children[0].children.length).to.deep.equal(vm.slides.length) 254 | done() 255 | }) 256 | }) 257 | }) 258 | 259 | // 多个 SSR 平铺测试 placeholder: 'ssr placeholder' 260 | describe('Multi edirot directive instance', () => { 261 | it(' - should update value after any change text', done => { 262 | const eventLogs = [] 263 | const vm = new Vue({ 264 | template: `
265 |
269 |
270 |
271 | {{ slide }} 272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 | `, 280 | data: { 281 | swipers: { 282 | a: [1, 2, 3], 283 | b: [4, 5, 6], 284 | c: [7, 8, 9] 285 | } 286 | }, 287 | methods: { 288 | buildOptions(key) { 289 | const options = {} 290 | if (key === 'a') { 291 | options.pagination = { el: '.pagi-a' } 292 | } 293 | if (key === 'b') { 294 | options.pagination = { el: '.pagi-b' } 295 | options.navigatio = { 296 | nextEl: '.swiper-button-next', 297 | prevEl: '.swiper-button-prev' 298 | } 299 | } 300 | if (key === 'c') { 301 | options.pagination = { el: '.pagi-c' } 302 | } 303 | options.on = { 304 | init() { 305 | eventLogs.push(`ssr/init/${key}`) 306 | } 307 | } 308 | return options 309 | } 310 | }, 311 | mounted() { 312 | eventLogs.push('ssr/mounted') 313 | } 314 | }).$mount() 315 | expect(eventLogs[0]).to.deep.equal('ssr/init/a') 316 | expect(eventLogs[1]).to.deep.equal('ssr/init/b') 317 | expect(eventLogs[2]).to.deep.equal('ssr/init/c') 318 | expect(eventLogs[3]).to.deep.equal('ssr/mounted') 319 | expect(vm['swiper-a'] instanceof Swiper).to.deep.equal(true) 320 | expect(vm['swiper-b'] instanceof Swiper).to.deep.equal(true) 321 | expect(vm['swiper-c'] instanceof Swiper).to.deep.equal(true) 322 | expect(vm['swiper-a'].el.children[1].outerHTML).to.deep.equal('
') 323 | expect(vm['swiper-b'].el.children[1].className).to.deep.equal('swiper-button-prev') 324 | expect(vm['swiper-b'].el.children[2].className).to.deep.equal('swiper-button-next') 325 | expect(vm['swiper-b'].el.children[3].className).to.deep.equal('swiper-pagination swiper-pagination-bullets pagi-b') 326 | expect(vm['swiper-c'].el.children.length).to.deep.equal(2) 327 | Vue.nextTick(() => { 328 | expect(typeof vm['swiper-c'].slideTo).to.equal('function') 329 | done() 330 | }) 331 | }) 332 | }) 333 | }) 334 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | root: true, 4 | parser: 'babel-eslint', 5 | globals: { 6 | env: false 7 | }, 8 | env: { 9 | browser: true, 10 | node: true 11 | }, 12 | parserOptions: { 13 | sourceType: 'module' 14 | }, 15 | // https:// github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 16 | extends: [ 17 | 'standard' 18 | ], 19 | // required to lint *.vue files 20 | plugins: [ 21 | 'html' 22 | ], 23 | // add your custom rules here 24 | rules: { 25 | 26 | /*Possible Errors*/ 27 | 28 | // 数组和对象键值对最后一个逗号, 29 | 30 | // never参数:不能带末尾的逗号, 31 | 32 | // always参数:必须带末尾的逗号, 33 | 34 | // always-multiline:多行模式必须带逗号,单行模式不能带逗号 35 | "comma-dangle": [0, "never"], 36 | 37 | // 禁止在条件表达式中使用赋值语句 38 | "no-cond-assign": 2, 39 | 40 | // 禁止使用console 41 | "no-console": 0, 42 | 43 | // 禁止在条件中使用常量表达式 if(true) if(1) 44 | "no-constant-condition": 2, 45 | 46 | // 禁止在正则表达式中使用控制符 47 | "no-control-regex": 2, 48 | 49 | // 禁止使用debugger语句 50 | "no-debugger": process.env.NODE_ENV === 'production' ? 2 : 0, 51 | 52 | // 函数参数禁止重名 53 | "no-dupe-args": 2, 54 | 55 | // 在创建对象字面量时不允许键重复 56 | "no-dupe-keys": 2, 57 | 58 | // 在switch语句中禁止重复的case 59 | "no-duplicate-case": 2, 60 | 61 | // 代码块的内容不能为空,禁止空代码块 62 | "no-empty": 2, 63 | 64 | // 正则表达式的内容不能为空,禁止使用不匹配任何字符串的正则表达式 65 | "no-empty-character-class": 2, 66 | 67 | // 禁止对catch语句中的异常进行赋值 68 | "no-ex-assign": 2, 69 | 70 | // 禁止不必要的bool转换 71 | "no-extra-boolean-cast": 2, 72 | 73 | // 禁止使用多余的圆括号 74 | "no-extra-parens": 2, 75 | 76 | // 禁止多余的冒号 77 | "no-extra-semi": 2, 78 | 79 | // 禁止重复的函数声明 80 | "no-func-assign": 2, 81 | 82 | // 禁止在块语句中声明变量或函数 83 | "no-inner-declarations": 2, 84 | 85 | // 禁止使用无效的正则语句 86 | "no-invalid-regexp": 2, 87 | 88 | // 禁止使用不合法或者不规则的空白符 89 | "no-irregular-whitespace": 2, 90 | 91 | // 在in操作符左边的操作项不能用! 例如这样写不对的:if ( !a in b) { // dosomething } 92 | "no-negated-in-lhs": 2, 93 | 94 | // 禁止把全局对象当函数调用,比如下面写法错误的:Math(), JSON() 95 | "no-obj-calls": 2, 96 | 97 | // 禁止在正则表达式字面量中使用多个空格 /foo bar/ 98 | "no-regex-spaces": 2, 99 | 100 | // 禁止稀疏数组,清除多余的逗号申明 比如[1,,2] 101 | "no-sparse-arrays": 2, 102 | 103 | // 为了保证两行不相关的代码不会意外的被当做一行代码来解析 104 | "no-unexpected-multiline": 0, 105 | 106 | // 禁止有执行不到的代码 107 | "no-unreachable": 2, 108 | 109 | // 禁止和NaN作比较,推荐使用isNaN方法 110 | "use-isnan": 2, 111 | 112 | // 用来检测JSDoc是否完整和合法 113 | "valid-jsdoc": 2, 114 | 115 | // typeof操作符返回的结果会是 "undefined", "object", "boolean", "number", "string", 和 "function"之一。 116 | 117 | // 保证typeof 操作符返回的结果必须和上面六个字符串作比较 118 | "valid-typeof": 2, 119 | 120 | /*Best Practices*/ 121 | 122 | // 在声明对象时getter和setter需成对出现 123 | "accessor-pairs": 2, 124 | 125 | // 数值方法的回调函数中强制写return语句 126 | "array-callback-return": 2, 127 | 128 | // 当在代码块中用var声明变量,并在代码块外使用时报错 129 | "block-scoped-var": 0, 130 | 131 | // 用来控制函数的复杂度,分支超过5时报错 132 | "complexity": [0, 5], 133 | 134 | // 不同分支的return语句不能返回不同的类型,要么一致要么都没有 135 | "consistent-return": 0, 136 | 137 | // if else while for do后面的代码块是否需要{ }包围,参数: 138 | 139 | // multi 只有块中有多行语句时才需要{ }包围 140 | 141 | // multi-line 只有块中有多行语句时才需要{ }包围, 但是块中的执行语句只有一行时,块中的语句只能跟和if语句在同一行。 142 | 143 | // if (foo) foo++; else doSomething(); 144 | 145 | // multi-or-nest 只有块中有多行语句时才需要{ }包围, 如果块中的执行语句只有一行,执行语句可以另起一行也可以跟在if语句后面 146 | 147 | // [2, "multi", "consistent"] 保持前后语句的{ }一致 148 | 149 | // default: [2, "all"] 全都需要{ }包围 150 | "curly": 2, 151 | 152 | // 所有的switch语句都必须要有一个default分支 153 | "default-case": 2, 154 | 155 | // 在书写对象的属性或方法时,新的一行代码可以以. 开头,也可以以. 结束。 156 | 157 | // 强制统一object.key中 . 的位置,参数: 158 | 159 | // property,'.'号应与属性在同一行 160 | 161 | // object, '.' 号应与对象名在同一行 162 | "dot-location": [2, "property"], 163 | 164 | // 强制使用.号取属性 165 | 166 | // 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性 167 | 168 | // false 使用保留字做属性名时, 只能使用[]方式取属性 169 | 170 | // e.g [2, {"allowKeywords": false}] 171 | 172 | // allowPattern: 当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 173 | 174 | // e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}] 175 | "dot-notation": [2, { "allowKeywords": true }], 176 | 177 | // 在进行比较时,必须使用全等=== 和完全不等!== 178 | "eqeqeq": [0, "allow-null"], 179 | 180 | // 在for-in 循环中要使用if语句 181 | "guard-for-in": 2, 182 | 183 | // 代码中禁止使用alert, confirm, and prompt 184 | "no-alert": 0, 185 | 186 | // 禁止使用arguments.caller和arguments.callee 187 | "no-caller": 2, 188 | 189 | // 禁止在case/default语句中使用lexical declarations,例如let, const, function and class 190 | 191 | // 因为在case/default中的声明,在整个switch语句中都能够访问到,如果需要声明变量,可以加大括号。 192 | "no-case-declarations": 2, 193 | 194 | // 不能使用看起来像除法的正则表达式 195 | 196 | // 用来消除/ (除号)操作符对程序员的迷惑,比如在正则表达式/=foo/中,我们并不能够确定第一个/是除号还是正则表达式,因此我们需要在等号前面加一个转移符/\=foo/ 197 | "no-div-regex": 2, 198 | 199 | // 在if else语句中,如果else语句中只含有一个return语句,那么完全可以不使用else语句,直接return。 200 | "no-else-return": 0, 201 | 202 | // 不允许空函数 203 | "no-empty-function": 0, 204 | 205 | // 在结构赋值时,模式不能为空。在ECMAScript2015的结构赋值中,模式为空是不会报错的,只是这样的结构赋值没有任何效果,该条规则就保证了模式不能为空,也就保证了结构赋值的有效性。 206 | "no-empty-pattern": 2, 207 | 208 | // 保证了在和null比较时使用===和!==,而不能够使用==和!= 209 | "no-eq-null": 0, 210 | 211 | // 禁止使用eval函数 212 | "no-eval": 2, 213 | 214 | // 禁止扩展native对象,不能向native的对象上面添加属性 215 | "no-extend-native": 2, 216 | 217 | // 保证了调用bind方法的函数体内有this对象。规避了不必要的使用bind方法的情况。 218 | 219 | // 箭头函数中没有this对象,也就不能够使用bind()方法。该规则保证了在所有的箭头函数中使用bind方法将被视为错误。 220 | "no-extra-bind": 2, 221 | 222 | // 如果 loop中没有内嵌的loops或switches, loop标签是不必要的. 223 | "no-extra-label": 2, 224 | 225 | // 在case语句中尽量加break,避免不必要的fallthrough错误,消除从一个case到另一个case的非故意的「fall through」。 226 | 227 | // 如果没有添加break等终止语句或者没有添加注释语句,将会抛出错误 228 | "no-fallthrough": 2, 229 | 230 | // 在使用浮点小数时,不能够省略小数点前面的数或者后面的数,必须写。比如.2 2. 应该写2.2 2.0 231 | "no-floating-decimal": 2, 232 | 233 | // 禁止隐式转换,为了消除简写的类型转换 234 | "no-implicit-coercion": 2, 235 | 236 | // 禁止在全局作用域里声明变量或函数 237 | "no-implicit-globals": 2, 238 | 239 | // 在setTimeout(), setInterval() or execScript()中消除隐式eval的使用 240 | "no-implied-eval": 2, 241 | 242 | // 禁止无效的this,只能用在构造器,类,对象字面量 243 | "no-invalid-this": 0, 244 | 245 | // 禁止使用__iterator__属性 246 | "no-iterator": 2, 247 | 248 | // 禁止使用label语句,以避免无限循环 249 | "no-labels": [2, { "allowLoop": false, "allowSwitch": false }], 250 | 251 | // 禁止使用不必要的嵌套代码块 252 | "no-lone-blocks": 2, 253 | 254 | // 禁止在循环体中定义函数并且函数引用了外部变量 255 | 256 | // 在循环中定义了函数,但是函数内部没有引用外部变量,或者使用let定义的代码块变量,视为合法 257 | "no-loop-func": 2, 258 | 259 | // 禁止使用魔法数字,建议使用常量来代替 260 | "no-magic-numbers": 0, 261 | 262 | // 保证了在逻辑表达式、条件表达式、申明语句、数组元素、对象属性、sequences、函数参数中不使用超过一个的空白符。 263 | "no-multi-spaces": 0, 264 | 265 | // 该规则保证了字符串不分行书写。 266 | "no-multi-str": 2, 267 | 268 | // 该规则保证了不重写原生对象。 269 | "no-native-reassign": 2, 270 | 271 | // 在使用new来调用构造函数后,必须把生成的实例赋值给一个变量 272 | "no-new": 2, 273 | 274 | // 禁止使用new Function(); 语句。 275 | "no-new-func": 2, 276 | 277 | // 禁止使用new创建String,Number, and Boolean实例 278 | "no-new-wrappers": 2, 279 | 280 | // 禁止使用八进制数字 281 | "no-octal": 2, 282 | 283 | // 禁止使用八进制转义序列,比如 var foo = "Copyright \251"; 284 | "no-octal-escape": 2, 285 | 286 | // 禁止对函数的参数重新进行无意义的赋值 287 | "no-param-reassign": 0, 288 | 289 | // 禁止使用__proto__属性 290 | "no-proto": 2, 291 | 292 | // 避免重复声明一个变量 293 | "no-redeclare": [2, { "builtinGlobals": true }], 294 | 295 | // 不要在return语句中使用赋值语句 296 | "no-return-assign": [2, "always"], 297 | 298 | // 禁止代码中使用类似javascript:void(0)的javascript: urls. 299 | "no-script-url": 2, 300 | 301 | // 禁止给自身赋值 302 | "no-self-assign": 2, 303 | 304 | // 禁止和自身作比较 305 | "no-self-compare": 2, 306 | 307 | // 禁止可能导致结果不明确的逗号操作符 308 | "no-sequences": 0, 309 | 310 | // 通过throw语句抛出的对象必须是Error对象本身或者通过Error对象定义的对象。有些情况除外,见官网 311 | "no-throw-literal": 2, 312 | 313 | // 禁止使用不被修改的循环条件 314 | "no-unmodified-loop-condition": 2, 315 | 316 | // 禁止在代码中出现没有被使用到的表达式或值 317 | "no-unused-expressions": [2, { "allowShortCircuit": true, "allowTernary": true }], 318 | 319 | // 禁止在代码中出现没有被使用到的标签 320 | "no-unused-labels": 2, 321 | 322 | // 避免使用没有意义的call() 和 apply() 323 | "no-useless-call": 2, 324 | 325 | // 避免使用不必要的字符串拼接 326 | "no-useless-concat": 2, 327 | 328 | // 不要使用void操作符 329 | "no-void": 2, 330 | 331 | // 生产代码中不能出现warning-comments包含的注释 332 | "no-warning-comments": [2, { "terms": ["todo", "fixme", "any other term"], "location": "anywhere" }], 333 | 334 | // 不要使用with语句 335 | "no-with": 2, 336 | 337 | // 在使用parseInt()方法时,必须要传递第二个参数来帮助解析。 338 | "radix": 2, 339 | 340 | // 在通过var声明变量时,应该放在代码所在作用域的顶部 341 | "vars-on-top": 2, 342 | 343 | // 立即执行函数需要通过圆括号包围 344 | "wrap-iife": 2, 345 | 346 | // yoda条件语句就是对象字面量应该写在比较操作符的左边,而变量应该写在比较操作符的右边 347 | 348 | // 默认的规则要求,变量写在左边而字面量写在右边 349 | "yoda": 2, 350 | 351 | 352 | /*Strict Mode*/ 353 | 354 | // 使用严格模式 355 | "strict": 2, 356 | 357 | /*Variables*/ 358 | 359 | // 变量声明时必须赋初值 360 | "init-declarations": 0, 361 | 362 | // In IE 8 and earlier,禁止catch子句参数与外部作用域变量同名 363 | "no-catch-shadow": 2, 364 | 365 | // 禁止使用delete删除var声明的变量 366 | "no-delete-var": 0, 367 | 368 | // 防止label和声明的变量重名 369 | "no-label-var": 2, 370 | 371 | // 禁止使用某些全局变量 372 | "no-restricted-globals": [2, "event"], 373 | 374 | // 禁止声明外部作用域中已定义的变量 375 | "no-shadow": 0, 376 | 377 | // 声明变量时禁止覆盖JavaScript中的一些保留关键字,比如NaN、Infinity、undefined、eval、arguments等。 378 | "no-shadow-restricted-names": 2, 379 | 380 | // 禁止使用未被定义的变量,除非已在配置文件的global中进行了说明。 381 | "no-undef": 2, 382 | 383 | // 禁止初始化变量为undefined 384 | "no-undef-init": 2, 385 | 386 | // 禁止把undefined作为变量名 387 | "no-undefined": 0, 388 | 389 | // 不允许定义的变量在后面的代码中没有被使用到 390 | "no-unused-vars": 0, 391 | 392 | // 所有的变量都应该先定义后使用 393 | "no-use-before-define": 0, 394 | 395 | 396 | /*Node.js and CommonJS*/ 397 | 398 | // 强制回调后return,避免多次调用回调 399 | "callback-return": 0, 400 | 401 | // 强制require()出现在模块作用域的顶部 402 | "global-require": 0, 403 | 404 | // 如果函数有err入参(err或者error),在函数体内必须进行处理 405 | "handle-callback-err": [0, "^(err|error)$"], 406 | 407 | // 声明时不能混用声明类型 408 | "no-mixed-requires": 0, 409 | 410 | // 禁止把require方法和new操作符一起使用。 411 | "no-new-require": 0, 412 | 413 | // 不能使用__dirname或__filename做路径拼接 414 | "no-path-concat": 0, 415 | 416 | // 禁止使用process.env 417 | "no-process-env": 0, 418 | 419 | // 禁止使用process.exit() 420 | "no-process-exit": 0, 421 | 422 | // 禁用使用指定模块,使用了就会报错 423 | "no-restricted-modules": [0, "fs"], 424 | 425 | // 禁止使用同步方法,建议使用异步方法 426 | "no-sync": 0, 427 | 428 | /*Stylistic Issues*/ 429 | 430 | // 用数组字面量定义数组时数组元素前后是否加空格, 431 | 432 | // never参数: 数组元素前后不能带空格, 433 | 434 | // always参数:数组元素前后必须留空格 435 | "array-bracket-spacing": [0, "never"], 436 | 437 | // 在单行代码块中,代码块前后是否需要留空格 438 | 439 | // always参数:默认,前后必须留空格 440 | 441 | // never参数: 前后不能带空格 442 | "block-spacing": [2, "always"], 443 | 444 | // 大括号的样式,比如下面的大括号语法采用『1tbs』,允许单行样式 445 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 446 | 447 | // 强制使用驼峰命名 448 | "camelcase": 0, 449 | 450 | // 规定了逗号前后的空白,默认配置规定逗号前面没有空白,而逗号后面需要留空白 451 | "comma-spacing": [2, { "before": false, "after": true }], 452 | 453 | // 规定了逗号放的位置,默认配置逗号应该放在行末,如果设置为first,逗号就应放在行首 454 | "comma-style": [2, "last"], 455 | 456 | // 是否在对象的动态属性(computed properties: ES6引入)中添加空白,默认配置不添加空白 457 | "computed-property-spacing": [2, "never"], 458 | 459 | // 统一this的别名(this赋值的变量名)保证整个应用程序代码的统一。 460 | 461 | // 如果一个变量被指定为this对象的别名,那么这个变量就不能够用来赋其他值,只能够用来保存this对象。 462 | 463 | // 如果this对象明确被赋值给了一个变量,那么这个变量应该是配置中指定的那个变量名。 464 | "consistent-this": [0, "self"], 465 | 466 | // 该规则规定文件最后强制换行,仅需留一空行 467 | "eol-last": 2, 468 | 469 | // 要求给函数表达式命名,便于debug 470 | "func-names": 0, 471 | 472 | // 在JavaScript中有两种方式定义函数:函数声明和函数表达式。 473 | 474 | // 函数声明就是把function关键词写在最前面,后面跟一个函数名。我们可以在函数申明代码前调用函数 475 | 476 | // 函数表达式是通过var等声明变量的关键字开头,然后跟函数名,再后面是function本身。在使用函数表达式定义函数前调用函数会报错 477 | 478 | // 统一定义函数是所采用的方式,参数: 479 | 480 | // declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"] 481 | 482 | // expression:强制使用方法表达式的方式,默认方式,var f = function() {} e.g [2, "expression"] 483 | 484 | // allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", {"allowArrowFunctions":true}] 485 | "func-style": [2, "expression"], 486 | 487 | // 规定了标识符命名的黑名单 488 | "id-blacklist": [0, "data", "err", "e", "cb", "callback"], 489 | 490 | // 规定标识符的长度,默认配置标识符最少两个字符 491 | "id-length": [2, { "min": 1 }], 492 | 493 | // 命名检测,标识符命名需和配置中的正则表达式匹配,但是该规则对函数调用无效。 494 | "id-match": [0, "^[a-z]+([A-Z][a-z]+)*$", { "properties": false }], 495 | 496 | // 统一代码缩进方式,默认值是4 spaces. 497 | "indent": 0, 498 | 499 | // 规定了在JSX中的属性值是使用单引号还是双引号,默认使用双引号 500 | "jsx-quotes": [2, "prefer-double"], 501 | 502 | // 该规则规定了在对象字面量语法中key和value之间的空白,冒号前不要留空格,冒号后面需留一个空格 503 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 504 | 505 | // 规定了keyword前后是否需要留一个空格 506 | "keyword-spacing": [0, { "before": true, "after": true, "overrides": {} }], 507 | 508 | // 统一换行符,"\n" unix(for LF) and "\r\n" for windows(CRLF),默认unix 509 | "linebreak-style": 0, 510 | 511 | // 规定注释和代码块之间是否留空行 512 | "lines-around-comment": 0, 513 | 514 | // 规定代码最多可以嵌套多少层 515 | "max-depth": [2, 4], 516 | 517 | // 规定了代码单行的最大长度 518 | "max-len": [0, 80, 4], 519 | 520 | // 规定了回调的最大嵌套层数 521 | "max-nested-callbacks": [2, 10], 522 | 523 | // 规定了函数参数的最大个数 524 | "max-params": [0, 3], 525 | 526 | // 规定了函数中代码不能够超过多少行 527 | "max-statements": [0, 10], 528 | 529 | // 使用构造函数(new)时首字母需大写,首字母大写的函数需用new操作符 530 | "new-cap": 2, 531 | 532 | // 使用构造函数(new)时必须圆括号不能省略 533 | "new-parens": 0, 534 | 535 | // 规定了变量声明后是否需要空行 536 | "newline-after-var": 0, 537 | 538 | // 规定了return语句前是否是否需要空行 539 | "newline-before-return": 0, 540 | 541 | // 规定了方法链式调用时是否需换行 542 | "newline-per-chained-call": 0, 543 | 544 | // 禁止使用Array构造函数 545 | "no-array-constructor": 0, 546 | 547 | // 禁止使用位操作符 548 | "no-bitwise": 0, 549 | 550 | // 禁止使用continue 551 | "no-continue": 0, 552 | 553 | // 禁止使用行内注释 554 | "no-inline-comments": 0, 555 | 556 | // 禁止在if-else控制语句中,else代码块中仅包含一个if语句 557 | "no-lonely-if": 0, 558 | 559 | // 禁止混用tab和空格 560 | "no-mixed-spaces-and-tabs": 2, 561 | 562 | // 不要留超过规定数目的空白行 563 | "no-multiple-empty-lines": [2, { "max": 3 }], 564 | 565 | // 在if语句中使用了否定表达式,同时else语句又不为空,那么这样的if-else语句将被视为不合法,为什么不将其反过来这样代码更容易理解,该规则同样适用于三元操作符 566 | "no-negated-condition": 0, 567 | 568 | // 三元操作符禁止嵌套 569 | "no-nested-ternary": 0, 570 | 571 | // 禁止使用new Object()来构造对象 572 | "no-new-object": 0, 573 | 574 | // 禁止使用++,-- 575 | "no-plusplus": 0, 576 | 577 | // 禁止使用某些特定的JavaScript语法,例如FunctionDeclaration 和 WithStatement 578 | "no-restricted-syntax": [0, "FunctionExpression", "WithStatement"], 579 | 580 | // 函数调用时,函数名和圆括号之间不能有空格 581 | "no-spaced-func": 2, 582 | 583 | // 禁止使用三元操作符 584 | "no-ternary": 0, 585 | 586 | // 禁止行末加空格 587 | "no-trailing-spaces": 0, 588 | 589 | // 禁止在标识符前后使用下划线 590 | "no-underscore-dangle": 0, 591 | 592 | // 禁止使用没有必要的三元操作符,因为用有些三元操作符可以使用其他语句替换 593 | "no-unneeded-ternary": [0, { "defaultAssignment": false }], 594 | 595 | // 禁止属性操作符.的前后和[之前有空格 596 | "no-whitespace-before-property": 2, 597 | 598 | // 规定对象字面量中大括号内是否允许加空格,也适用于ES6中的结构赋值和模块import和export 599 | "object-curly-spacing": [0, "always"], 600 | 601 | // 规定了在每个函数中声明变量是否只使用一次var,该规则同样适用于let和const 602 | "one-var": [2, { "initialized": "never" }], 603 | 604 | // 规定了使用赋值操作符的简写形式 605 | "operator-assignment": [2, "always"], 606 | 607 | // 在换行时操作符应该放在行首还是行尾。还可对某些操作符进行重写。 608 | "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], 609 | 610 | // 在代码块中,代码块的开始和结尾是否应该留一个空行 611 | "padded-blocks": 0, 612 | 613 | // 对象的属性名是否强制加双引号 614 | "quote-props": [0, "always"], 615 | 616 | // 在JavaScript中有三种方式定义字符串,双引号、单引号、反义符(ECMAScript2015)。规定了字符串定义的方式 617 | "quotes": [0, "single", "avoid-escape"], 618 | 619 | // 注释格式要求JSDoc格式 620 | "require-jsdoc": [0, { 621 | 622 | "require": { 623 | 624 | "FunctionDeclaration": true, 625 | 626 | "MethodDefinition": false, 627 | 628 | "ClassDeclaration": false 629 | 630 | } 631 | 632 | }], 633 | 634 | // JavaScript不要求在每行末尾加上分号,这是因为JavaScript引擎会决定是否需要在行末加上分号,然后自动帮我们在行末加上分号,这一特性被成为ASI(automatic semicolon insertion),也是JavaScript语言最富争议的特性之一 635 | 636 | // 尽管ASI允许我们使用更加自由的代码风格,但是它也可能使得你的代码并不是按你期许的方式运行 637 | 638 | // 两个可选参数,always 和never 639 | 640 | // 默认配置always,要求在行末加上分号。 641 | "semi": [0, "always"], 642 | 643 | // 该规则用来规定分号前后是否加空格,默认配置如下 644 | "semi-spacing": [2, { "before": false, "after": true }], 645 | 646 | // 要求对同一个模块里的import声明按字母排序 647 | "sort-imports": 0, 648 | 649 | // 规定在同一个变量声明代码块中,要对变量的声明按字母排序 650 | "sort-vars": 2, 651 | 652 | // 规定了在代码块前是否需要加空格 653 | "space-before-blocks": [2, "always"], 654 | 655 | // 函数定义时,function关键字后面的小括号前是否需要加空格 656 | "space-before-function-paren": [0, "always"], 657 | 658 | // 规定圆括号内部的空格。规定是否需要在(右边,或者)左边加空格。 659 | "space-in-parens": [2, "never"], 660 | 661 | // 中綴操作符左右是否添加空格 662 | "space-infix-ops": 2, 663 | 664 | // 规定在一元操作符前后是否需要加空格,单词类操作符需要加,而非单词类操作符不用加 665 | 666 | // words - applies to unary word operators such as: new, delete, typeof, void, yield 667 | 668 | // nonwords - applies to unary operators such as: -, +, --, ++, !, !! 669 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 670 | 671 | // 规定是否需要在代码注释起始符// or /*后面至少紧跟一个空格 672 | "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], 673 | 674 | // 要求在正则表达式的双斜杠外面加一个圆括号,来消除歧义 675 | "wrap-regex": 0, 676 | 677 | 678 | /*ECMAScript 6*/ 679 | 680 | // 箭头函数中,如果函数体里只有一句代码时可以省略大括号 681 | 682 | // 规定是否可以省略大括号 683 | "arrow-body-style": 0, 684 | 685 | // 箭头函数中,只有一个参数时可以省略圆括号 686 | 687 | // 规定了参数是否需要圆括号包围 688 | "arrow-parens": [0, "always"], 689 | 690 | // 规定了箭头函数的箭头前后是否加空格 691 | "arrow-spacing": [2, { "before": true, "after": true }], 692 | 693 | // 保证constructor函数中super()应正确出现,比如在继承的classes中(派生类)必须使用super,否则(非派生类)不要使用super。 694 | "constructor-super": 2, 695 | 696 | // 规定generator函数中星号前后的空白 697 | "generator-star-spacing": [2, { "before": true, "after": true }], 698 | 699 | // 禁止覆盖class命名,也就是说变量名不要和class名重名 700 | "no-class-assign": 2, 701 | 702 | // 箭头函数的箭头和比较操作符 (>, <, <=, and >=)很相似,该规则要求在和比较操作符容易发生混淆时禁止使用箭头函数语法 703 | "no-confusing-arrow": 2, 704 | 705 | // 禁止修改const声明的变量 706 | "no-const-assign": 2, 707 | 708 | // class中的成员不允许有相同的名字 709 | "no-dupe-class-members": 2, 710 | 711 | // 禁止在Symbol对象前使用new操作符 712 | "no-new-symbol": 2, 713 | 714 | // 该规则可以定义不允许在应用中导入的模块 715 | "no-restricted-imports": [2, 716 | 717 | "assert", "buffer", "child_process", "cluster", "crypto", "dgram", "dns", "domain", "events", "freelist", "fs", "http", "https", "module", "net", "os", "path", "punycode", "querystring", "readline", "repl", "smalloc", "stream", "string_decoder", "sys", "timers", "tls", "tracing", "tty", "url", "util", "vm", "zlib" 718 | 719 | ], 720 | 721 | // 在构造函数中,禁止在super()调用前使用this/super对象 722 | "no-this-before-super": 2, 723 | 724 | // ES2015提供了默认的空构造函数,禁止使用不必要的空构造函数 725 | "no-useless-constructor": 2, 726 | 727 | // 禁用var,用let和const代替var 728 | "no-var": 2, 729 | 730 | // ES6中提供了定义对象字面量的方法和属性的简写形式。强制要求在对象字面量中使用方法和属性的简写形式 731 | "object-shorthand": 0, 732 | 733 | // 函数作为函数的参数传入时,传入的函数需要是箭头函数 734 | 735 | // 箭头函数中的this对象直接绑定到了其外面包围的函数的this对象。 736 | "prefer-arrow-callback": 0, 737 | 738 | // 如果一个变量声明后不再被修改,那么应使用const来声明该变量 739 | "prefer-const": 1, 740 | 741 | // 推荐使用Reflect上的方法替代以前老方法 742 | "prefer-reflect": 0, 743 | 744 | // 在ES2015(ES6)中推荐使用剩余参数(...rest)代替arguments变量 745 | "prefer-rest-params": 0, 746 | 747 | // 在ES2015(ES6)中推荐使用扩展符替代apply()方法 748 | "prefer-spread": 2, 749 | 750 | // 在ES2015(ES6)中推荐使用模板代替以前的字符串拼接 751 | "prefer-template ": 0, 752 | 753 | // 生成器函数中必须有yield关键字,如果没有会报错。 754 | "require-yield": 2, 755 | 756 | // 模板字符串中使用${ 和 } 包含的表达式前后是否需要留空格,默认规则禁止花括号内有空格 757 | "template-curly-spacing": [2, "never"], 758 | 759 | // yield*表达式中的*号前后是否留空格,默认after,比如yield* other() 760 | "yield-star-spacing": [2, "after"] 761 | 762 | } 763 | } 764 | --------------------------------------------------------------------------------