diff --git a/.gitignore b/.gitignore index 74b2771747..38164ea5a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ .DS_Store npm-debug.log -node_modules/ -out/ -server/dist +node_modules +.vscode-test +dist +server/dist docs/.vuepress/dist *.zip diff --git a/.travis.yml b/.travis.yml index 2564b5ceca..0ac9e156f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,24 @@ language: node_js +os: + - osx + - linux node_js: 6 -before_script: - - npm install yarn -g + +before_install: + - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.9.4 + - export PATH=$HOME/.yarn/bin:$PATH + - if [ $TRAVIS_OS_NAME == "linux" ]; then + export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; + sh -e /etc/init.d/xvfb start; + sleep 3; + fi + +install: + - yarn - cd server && yarn - cd .. + +script: + - yarn test + cache: yarn diff --git a/.vscode/launch.json b/.vscode/launch.json index e621c5cca9..27a6bf2b7c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,11 @@ ], "internalConsoleOptions": "neverOpen", "sourceMaps": true, - "outFiles": ["${workspaceRoot}/out/**/*.js"] + "outFiles": ["${workspaceRoot}/dist/**/*.js"], + "smartStep": true, + "skipFiles": [ + "/**" + ] }, { "name": "server", @@ -27,7 +31,29 @@ "sourceMaps": true, "outFiles": ["${workspaceRoot}/server/dist/**/*.js"], "protocol": "inspector", - "restart": true + "restart": true, + "smartStep": true, + "skipFiles": [ + "/**" + ] + }, + { + "name": "E2E Test", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/dist/test", + "${workspaceRoot}/test/fixture" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceRoot}/dist/test/**/*.js"], + "smartStep": true, + "skipFiles": [ + "/**" + ] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 5cf1c0e29b..2a70a0d926 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,6 @@ "files.exclude": { "server/dist": true, "docs/_book": true, - "client/out": true + "dist": true } } \ No newline at end of file diff --git a/package.json b/package.json index 51dba94ac9..e71524b6f5 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,10 @@ "prepush": "npm run test:server", "lint": "tslint -p tslint.json", "test:server": "cd server && npm run compile && npm test", - "test": "npm run lint && npm run test:server", + "test:e2e": "sh ./test/e2e.sh", + "test": "run-s lint compile test:server test:e2e", "docs": "bash ./build/update-docs.sh", - "postinstall": "if [[ ${NODE_ENV} != \"production\" ]]; then node ./node_modules/vscode/bin/install; fi" + "postinstall": "node ./node_modules/vscode/bin/install" }, "lint-staged": { "*.ts": [ @@ -39,7 +40,7 @@ "Programming Languages" ], "engines": { - "vscode": "^1.24.0" + "vscode": "^1.25.0" }, "dependencies": { "vscode-languageclient": "^3.4.2", @@ -48,7 +49,7 @@ "activationEvents": [ "onLanguage:vue" ], - "main": "./out/vueMain", + "main": "./dist/client/vueMain", "contributes": { "commands": [ { @@ -308,9 +309,11 @@ } }, "devDependencies": { + "@types/mocha": "^5.2.5", "@types/node": "^8.0.51", "husky": "^0.14.3", "lint-staged": "^6.0.0", + "mocha": "^5.2.0", "npm-run-all": "^4.1.3", "tslint": "^5.8.0", "typescript": "^2.8.1", diff --git a/server/src/vueServerMain.ts b/server/src/vueServerMain.ts index b773e70249..39619bdabc 100644 --- a/server/src/vueServerMain.ts +++ b/server/src/vueServerMain.ts @@ -42,13 +42,14 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { documentFormattingProvider: true, hoverProvider: true, documentHighlightProvider: true, + documentLinkProvider: true, documentSymbolProvider: true, definitionProvider: true, referencesProvider: true, colorProvider: true }; - return { capabilities }; + return { capabilities: (capabilities as any) }; }); connection.listen(); diff --git a/test/e2e.sh b/test/e2e.sh new file mode 100755 index 0000000000..0034a9a537 --- /dev/null +++ b/test/e2e.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +export CODE_TESTS_PATH="$(pwd)/dist/test" +export CODE_TESTS_WORKSPACE="$(pwd)/test/fixture" + +CODE_ROOT=$(pwd) + +if [ ! -d "$(pwd)/test/fixture/node_modules" ]; then + cd $CODE_TESTS_WORKSPACE + yarn install + cd $CODE_ROOT +fi + +node "$(pwd)/node_modules/vscode/bin/test" \ No newline at end of file diff --git a/test/fixture/.vscode/settings.json b/test/fixture/.vscode/settings.json new file mode 100644 index 0000000000..95bebc7014 --- /dev/null +++ b/test/fixture/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "vetur.format.defaultFormatter.html": "js-beautify-html" +} \ No newline at end of file diff --git a/test/fixture/LICENSE b/test/fixture/LICENSE new file mode 100644 index 0000000000..c2e18cffb4 --- /dev/null +++ b/test/fixture/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-present, Pine Wu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/fixture/README.md b/test/fixture/README.md new file mode 100644 index 0000000000..f712862681 --- /dev/null +++ b/test/fixture/README.md @@ -0,0 +1,28 @@ +# Veturpack + +Project based on [vuepack](https://github.com/egoist/vuepack) to try [Vetur](https://github.com/octref/vetur). + +## Usage + +```bash +$ yarn +$ yarn dev +``` + +## Things to Try + +- Do a emmet expansion on the html template. +- Try `_.` in `Counter.vue` to see lodash auto completion. +- Edit `.eslintrc` to config linting rules. +- Remove `// @ts-check` and add it back to see their difference. +- Format the document. +- Change some options in `vetur.format.*` then format again. +- Install another library with types, such as jquery. + - `npm i -S jquery && npm i -D @types/jquery`. + - Put `import * as $ from 'jquery'`. + - Try some jquery auto completion by typing `$.`. +- Try some auto completion in scss. + +## License + +MIT © [Pine Wu](https://github.com/octref) diff --git a/test/fixture/build/config.js b/test/fixture/build/config.js new file mode 100644 index 0000000000..2cb07d1b69 --- /dev/null +++ b/test/fixture/build/config.js @@ -0,0 +1,10 @@ +'use strict' +const pkg = require('../package') + +module.exports = { + port: 4000, + title: 'veturpack', + // when you use electron please set to relative path like ./ + // otherwise only set to absolute path when you're using history mode + publicPath: '/', +} diff --git a/test/fixture/build/index.html b/test/fixture/build/index.html new file mode 100644 index 0000000000..4bb81122a8 --- /dev/null +++ b/test/fixture/build/index.html @@ -0,0 +1,10 @@ + + + + + <%= htmlWebpackPlugin.options.title %> + + +
+ + \ No newline at end of file diff --git a/test/fixture/build/log-plugin.js b/test/fixture/build/log-plugin.js new file mode 100644 index 0000000000..a9bd395352 --- /dev/null +++ b/test/fixture/build/log-plugin.js @@ -0,0 +1,15 @@ +'use strict' +const chalk = require('chalk') + +// this plugin if for loggin url after each time the compilation is done. +module.exports = class LogPlugin { + constructor(port) { + this.port = port + } + + apply(compiler) { + compiler.plugin('done', () => { + console.log(`> VuePack is running at ${chalk.yellow(`http://localhost:${this.port}`)}\n`) + }) + } +} diff --git a/test/fixture/build/server.js b/test/fixture/build/server.js new file mode 100644 index 0000000000..8ffe72ce2b --- /dev/null +++ b/test/fixture/build/server.js @@ -0,0 +1,52 @@ +'use strict' +const fs = require('fs') +const path = require('path') +const chalk = require('chalk') +const express = require('express') +const webpack = require('webpack') +const webpackConfig = require('./webpack.dev') +const config = require('./config') +const LogPlugin = require('./log-plugin') + +const app = express() + +const port = config.port +webpackConfig.entry.client = [ + `webpack-hot-middleware/client?reload=true`, + webpackConfig.entry.client +] + +webpackConfig.plugins.push(new LogPlugin(port)) + +let compiler + +try { + compiler = webpack(webpackConfig) +} catch (err) { + console.log(err.message) + process.exit(1) +} + +const devMiddleWare = require('webpack-dev-middleware')(compiler, { + publicPath: webpackConfig.output.publicPath, + quiet: true +}) +app.use(devMiddleWare) +app.use(require('webpack-hot-middleware')(compiler, { + log: () => {} +})) + +const mfs = devMiddleWare.fileSystem +const file = path.join(webpackConfig.output.path, 'index.html') + + +devMiddleWare.waitUntilValid() + +app.get('*', (req, res) => { + devMiddleWare.waitUntilValid(() => { + const html = mfs.readFileSync(file) + res.end(html) + }) +}) + +app.listen(port) diff --git a/test/fixture/build/utils.js b/test/fixture/build/utils.js new file mode 100644 index 0000000000..a7700ac655 --- /dev/null +++ b/test/fixture/build/utils.js @@ -0,0 +1,68 @@ +'use strict' +const path = require('path') +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const config = require('./config') + +const _ = module.exports = {} + +_.cwd = (file) => { + return path.join(process.cwd(), file || '') +} + +_.cssLoader = config.cssModules ? + 'css-loader?-autoprefixer&modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' : + 'css-loader?-autoprefixer' + +_.cssProcessors = [ + {loader: '', test: /\.css$/}, + {loader: 'sass-loader?sourceMap', test: /\.scss$/}, + {loader: 'less-loader?sourceMap', test: /\.less$/}, + {loader: 'stylus-loader?sourceMap', test: /\.styl$/}, + {loader: 'sass-loader?indentedSyntax&sourceMap', test: /\.sass$/}, +] + +_.outputPath = config.electron ? + path.join(__dirname, '../app/dist') : + path.join(__dirname, '../dist') + +_.outputIndexPath = config.electron ? + path.join(__dirname, '../app/dist/index.html') : + path.join(__dirname, '../dist/index.html') + +_.target = config.electron ? + 'electron-renderer' : + 'web' + +// https://github.com/egoist/vbuild/blob/master/lib/vue-loaders.js +_.loadersOptions = () => { + const isProd = process.env.NODE_ENV === 'production' + + function generateLoader(langs) { + langs.unshift('css-loader?sourceMap&-autoprefixer') + if (!isProd) { + return ['vue-style-loader'].concat(langs).join('!') + } + return ExtractTextPlugin.extract({ + fallback: 'vue-style-loader', + use: langs.join('!') + }) + } + + return { + minimize: isProd, + options: { + // css-loader relies on context + context: process.cwd(), + vue: { + loaders: { + css: generateLoader([]), + sass: generateLoader(['sass-loader?indentedSyntax&sourceMap']), + scss: generateLoader(['sass-loader?sourceMap']), + less: generateLoader(['less-loader?sourceMap']), + stylus: generateLoader(['stylus-loader?sourceMap']), + js: 'babel-loader' + } + } + } + } +} diff --git a/test/fixture/build/webpack.base.js b/test/fixture/build/webpack.base.js new file mode 100644 index 0000000000..17f274e8cf --- /dev/null +++ b/test/fixture/build/webpack.base.js @@ -0,0 +1,82 @@ +'use strict' +const path = require('path') +const webpack = require('webpack') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const config = require('./config') +const _ = require('./utils') + +module.exports = { + entry: { + client: './client/index.js' + }, + output: { + path: _.outputPath, + filename: '[name].js', + publicPath: config.publicPath, + // Point sourcemap entries to original disk location + devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath), + // Add /* filename */ comments to generated require()s in the output. + pathinfo: true + }, + performance: { + hints: process.env.NODE_ENV === 'production' ? 'warning' : false + }, + resolve: { + extensions: ['.js', '.vue', '.css', '.json'], + alias: { + root: path.join(__dirname, '../client'), + components: path.join(__dirname, '../client/components') + }, + modules: [ + _.cwd('node_modules'), + // this meanse you can get rid of dot hell + // for example import 'components/Foo' instead of import '../../components/Foo' + _.cwd('client') + ] + }, + module: { + loaders: [ + { + test: /\.vue$/, + loaders: ['vue-loader'] + }, + { + test: /\.js$/, + loaders: ['babel-loader'], + exclude: [/node_modules/] + }, + { + test: /\.es6$/, + loaders: ['babel-loader'] + }, + { + test: /\.(ico|jpg|png|gif|eot|otf|webp|ttf|woff|woff2)(\?.*)?$/, + loader: 'file-loader', + query: { + name: 'static/media/[name].[hash:8].[ext]' + } + }, + { + test: /\.svg$/, + loader: 'raw-loader' + } + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + title: config.title, + template: path.resolve(__dirname, 'index.html'), + filename: _.outputIndexPath + }), + new webpack.LoaderOptionsPlugin(_.loadersOptions()), + new CopyWebpackPlugin([ + { + from: _.cwd('./static'), + // to the roor of dist path + to: './' + } + ]) + ], + target: _.target +} diff --git a/test/fixture/build/webpack.dev.js b/test/fixture/build/webpack.dev.js new file mode 100644 index 0000000000..aedcbeca67 --- /dev/null +++ b/test/fixture/build/webpack.dev.js @@ -0,0 +1,35 @@ +'use strict' +process.env.NODE_ENV = 'development' + +const webpack = require('webpack') +const base = require('./webpack.base') +const _ = require('./utils') +const FriendlyErrors = require('friendly-errors-webpack-plugin') + +base.devtool = 'source-map' +base.plugins.push( + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('development') + }), + new webpack.HotModuleReplacementPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + new FriendlyErrors() +) + +// push loader for css files +_.cssProcessors.forEach(processor => { + let loaders + if (processor.loader === '') { + loaders = ['postcss-loader'] + } else { + loaders = ['postcss-loader', processor.loader] + } + base.module.loaders.push( + { + test: processor.test, + loaders: ['style-loader', _.cssLoader].concat(loaders) + } + ) +}) + +module.exports = base diff --git a/test/fixture/build/webpack.prod.js b/test/fixture/build/webpack.prod.js new file mode 100644 index 0000000000..11118720a8 --- /dev/null +++ b/test/fixture/build/webpack.prod.js @@ -0,0 +1,92 @@ +'use strict' +process.env.NODE_ENV = 'production' + +const webpack = require('webpack') +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const ProgressPlugin = require('webpack/lib/ProgressPlugin') +const OfflinePlugin = require('offline-plugin') +const rm = require('rimraf') +const base = require('./webpack.base') +const pkg = require('../package') +const _ = require('./utils') +const config = require('./config') + +if (config.electron) { + // remove files in dist folder in electron mode + rm.sync('app/assets/*') +} else { + // remove dist folder in web app mode + rm.sync('dist/*') + // use source-map in web app mode + base.devtool = 'source-map' +} + +// use hash filename to support long-term caching +base.output.filename = '[name].[chunkhash:8].js' +// add webpack plugins +base.plugins.push( + new ProgressPlugin(), + new ExtractTextPlugin('styles.[contenthash:8].css'), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production') + }), + new webpack.optimize.UglifyJsPlugin({ + sourceMap: true, + compress: { + warnings: false + }, + output: { + comments: false + } + }), + // extract vendor chunks + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: module => { + return module.resource && /\.(js|css|es6)$/.test(module.resource) && module.resource.indexOf('node_modules') !== -1 + } + }), + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest' + }), + // progressive web app + // it uses the publicPath in webpack config + new OfflinePlugin({ + relativePaths: false, + AppCache: false, + ServiceWorker: { + events: true + } + }) +) + +// extract css in standalone css files +_.cssProcessors.forEach(processor => { + let loaders + if (processor.loader === '') { + loaders = ['postcss-loader'] + } else { + loaders = ['postcss-loader', processor.loader] + } + base.module.loaders.push({ + test: processor.test, + loader: ExtractTextPlugin.extract({ + use: [_.cssLoader].concat(loaders), + fallback: 'style-loader' + }) + }) +}) + +// minimize webpack output +base.stats = { + // Add children information + children: false, + // Add chunk information (setting this to `false` allows for a less verbose output) + chunks: false, + // Add built modules information to chunk information + chunkModules: false, + chunkOrigins: false, + modules: false +} + +module.exports = base diff --git a/test/fixture/client/app.js b/test/fixture/client/app.js new file mode 100644 index 0000000000..640393f794 --- /dev/null +++ b/test/fixture/client/app.js @@ -0,0 +1,15 @@ +import Vue from 'vue' +import { sync } from 'vuex-router-sync' +import App from './components/App.vue' +import router from './router' +import store from './store' + +sync(store, router) + +const app = new Vue({ + router, + store, + ...App +}) + +export { app, router, store } diff --git a/test/fixture/client/completion/script/Basic.vue b/test/fixture/client/completion/script/Basic.vue new file mode 100644 index 0000000000..1af25480c5 --- /dev/null +++ b/test/fixture/client/completion/script/Basic.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/test/fixture/client/completion/script/Framework.vue b/test/fixture/client/completion/script/Framework.vue new file mode 100644 index 0000000000..06ef8dad6c --- /dev/null +++ b/test/fixture/client/completion/script/Framework.vue @@ -0,0 +1,4 @@ +