diff --git a/README.md b/README.md index 3f5c978..884ce45 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # vue-cli-plugin-vue-next -A Vue CLI plugin for trying out the Vue 3 alpha. +A Vue CLI plugin for trying out the Vue 3 beta. This is for preview purposes only. There might be bugs and undocumented behavior differences from v2, which are expected. @@ -15,7 +15,7 @@ vue add vue-next ## What's implemented? -- [x] Add Vue 3 alpha and `@vue/compiler-sfc` to the project dependencies. +- [x] Add Vue 3 beta and `@vue/compiler-sfc` to the project dependencies. - [x] Configure webpack to compile `.vue` files with the new Vue 3 compiler. - [x] [Codemods](./generator/codemods/global-api) that automatically migrate some global API changes mentioned in [RFC-0009](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0009-global-api-change.md). - [x] Install Vuex 4.0 & Vue Router 4.0 in the project, if older versions of them are detected. diff --git a/generator/codemods/global-api/remove-vue-use.js b/generator/codemods/global-api/remove-vue-use.js index 119e085..cac39d6 100644 --- a/generator/codemods/global-api/remove-vue-use.js +++ b/generator/codemods/global-api/remove-vue-use.js @@ -22,5 +22,18 @@ module.exports = function removeVueUse({ j, root }) { } } }) - vueUseCalls.remove() + + const removablePlugins = ['VueRouter', 'Vuex'] + const removableUseCalls = vueUseCalls.filter(({ node }) => { + if (j.Identifier.check(node.arguments[0])) { + const plugin = node.arguments[0].name + if (removablePlugins.includes(plugin)) { + return true + } + } + + return false + }) + + removableUseCalls.remove() } diff --git a/generator/codemods/router/__testfixtures__/create-history.input.js b/generator/codemods/router/__testfixtures__/create-history.input.js index 508a091..e2b8d3e 100644 --- a/generator/codemods/router/__testfixtures__/create-history.input.js +++ b/generator/codemods/router/__testfixtures__/create-history.input.js @@ -20,4 +20,9 @@ const router = new VueRouter({ routes }); +new VueRouter({ + mode: 'history', + routes +}); + export default router; diff --git a/generator/codemods/router/__testfixtures__/create-history.output.js b/generator/codemods/router/__testfixtures__/create-history.output.js index 701c7d6..1e17aa3 100644 --- a/generator/codemods/router/__testfixtures__/create-history.output.js +++ b/generator/codemods/router/__testfixtures__/create-history.output.js @@ -15,8 +15,12 @@ const routes = [ ]; const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes +}); + +createRouter({ history: createWebHistory(), - base: process.env.BASE_URL, routes }); diff --git a/generator/codemods/router/__testfixtures__/create-router.input.js b/generator/codemods/router/__testfixtures__/create-router.input.js index ce236bf..e3aaecb 100644 --- a/generator/codemods/router/__testfixtures__/create-router.input.js +++ b/generator/codemods/router/__testfixtures__/create-router.input.js @@ -19,4 +19,8 @@ const router = new VueRouter({ routes }); +new VueRouter({ + routes +}); + export default router; diff --git a/generator/codemods/router/__testfixtures__/create-router.output.js b/generator/codemods/router/__testfixtures__/create-router.output.js index 7eabd05..eb97c4d 100644 --- a/generator/codemods/router/__testfixtures__/create-router.output.js +++ b/generator/codemods/router/__testfixtures__/create-router.output.js @@ -1,4 +1,4 @@ -import { createRouter } from 'vue-router'; +import { createRouter, createWebHashHistory } from 'vue-router'; import Home from '../views/Home.vue'; const routes = [ @@ -15,7 +15,12 @@ const routes = [ ]; const router = createRouter({ - base: process.env.BASE_URL, + history: createWebHashHistory(process.env.BASE_URL), + routes +}); + +createRouter({ + history: createWebHashHistory(), routes }); diff --git a/generator/codemods/router/index.js b/generator/codemods/router/index.js index 163ba9f..b640639 100644 --- a/generator/codemods/router/index.js +++ b/generator/codemods/router/index.js @@ -28,33 +28,47 @@ module.exports = function(fileInfo, api) { addImport(context, { imported: 'createRouter' }, 'vue-router') newVueRouter.replaceWith(({ node }) => { // mode: 'history' -> history: createWebHistory(), etc + let historyMode = 'createWebHashHistory' + let baseValue node.arguments[0].properties = node.arguments[0].properties.map(p => { if (p.key.name === 'mode') { const mode = p.value.value - let initializer if (mode === 'hash') { - initializer = 'createWebHashHistory' + historyMode = 'createWebHashHistory' } else if (mode === 'history') { - initializer = 'createWebHistory' + historyMode = 'createWebHistory' } else if (mode === 'abstract') { - initializer = 'createMemoryHistory' + historyMode = 'createMemoryHistory' } else { console.error( `mode must be one of 'hash', 'history', or 'abstract'` ) return p } - - addImport(context, { imported: initializer }, 'vue-router') - return j.objectProperty( - j.identifier('history'), - j.callExpression(j.identifier(initializer), []) - ) + return + } else if (p.key.name === 'base') { + baseValue = p.value + return } return p }) + // add the default mode with a hash history + addImport(context, { imported: historyMode }, 'vue-router') + node.arguments[0].properties = node.arguments[0].properties.filter( + p => !!p + ) + node.arguments[0].properties.unshift( + j.objectProperty( + j.identifier('history'), + j.callExpression( + j.identifier(historyMode), + baseValue ? [baseValue] : [] + ) + ) + ) + return j.callExpression(j.identifier('createRouter'), node.arguments) }) removeExtraneousImport(context, localVueRouter) diff --git a/generator/index.js b/generator/index.js index 2a0c325..d9f4604 100644 --- a/generator/index.js +++ b/generator/index.js @@ -2,12 +2,15 @@ const fs = require('fs') const path = require('path') module.exports = (api) => { + // need the CLI to support the `prune` option of `extendPackage` + api.assertCliVersion('>= 4.2.3') + api.extendPackage({ dependencies: { - vue: '^3.0.0-alpha.11' + vue: '^3.0.0-beta.1' }, devDependencies: { - '@vue/compiler-sfc': '^3.0.0-alpha.11', + '@vue/compiler-sfc': '^3.0.0-beta.1', // remove the vue-template-compiler 'vue-template-compiler': null } @@ -16,6 +19,9 @@ module.exports = (api) => { prune: true }) + const globalAPITransform = require('./codemods/global-api') + api.transformScript(api.entryFile, globalAPITransform) + if (api.hasPlugin('eslint')) { api.extendPackage({ devDependencies: { @@ -52,6 +58,24 @@ module.exports = (api) => { }) } + const resolveFile = (file) => { + if (!/\.(j|t)s$/ig.test(file)) { + file += '.js' + } + let resolvedPath = api.resolve(file) + const possiblePaths = [ + resolvedPath, + resolvedPath.replace(/\.js$/ig, '.ts'), + path.join(resolvedPath.replace(/\.js$/ig, ''), 'index.js'), + path.join(resolvedPath.replace(/\.js$/ig, ''), 'index.ts') + ] + for (const p of possiblePaths) { + if (fs.existsSync(p)) { + return path.relative(api.resolve('.'), p).replace(/\\/g, '/') + } + } + } + if (api.hasPlugin('vuex') || api.generator.pkg.dependencies['vuex']) { api.extendPackage({ dependencies: { @@ -60,73 +84,45 @@ module.exports = (api) => { }) api.exitLog('Installed vuex 4.0.') - api.exitLog('See the documentation at https://github.com/vuejs/vuex/tree/4.0') + api.exitLog('Documentation available at https://github.com/vuejs/vuex/tree/4.0') - // Codemod TODOs: - // * Remove `Vue.use(Vuex)` - // * `new Vue({ store })` -> `app.use(store)` - // * optional: `new Vuex.Store({})` -> `createStore({})` + const storePath = resolveFile('src/store') + if (storePath) { + api.transformScript(storePath, globalAPITransform) + api.transformScript(storePath, require('./codemods/vuex')) + } } if (api.hasPlugin('router') || api.generator.pkg.dependencies['router']) { api.extendPackage({ dependencies: { - 'vue-router': '^4.0.0-alpha.4' + 'vue-router': '^4.0.0-alpha.6' } }) api.exitLog('Installed vue-router 4.0.') - api.exitLog('See the documentation at https://github.com/vuejs/vue-router-next') - - // Codemod TODOs: - // * Remove `Vue.use(VueRouter)` - // * `new VueRouter({})` -> `createRouter({})` - // * `mode`: - // * `mode: 'history'` -> `history: createWebHistory()` - // * `mode: 'hash'` -> `history: createWebHashHistory()` - // * `mode: 'abstract'` -> `history: createMemoryHistory()` - // * `new Vue({ router })` -> `app.use(router)` - // * Async component syntax migration as described in RFC0007 - // * Create the corresponding imports - // * Remove unused imports (use ESLint for this task) + api.exitLog('Documentation available at https://github.com/vuejs/vue-router-next') - // Others: + const routerPath = resolveFile('src/router') + if (routerPath) { + api.transformScript(routerPath, globalAPITransform) + api.transformScript(routerPath, require('./codemods/router')) + } + + // Notes: // * Catch all routes (`/*`) must now be defined using a parameter with a custom regex: `/:catchAll(.*)` // * `keep-alive` is not yet supported // * Partial support of per-component navigation guards. No `beforeRouteEnter` } - const globalApiTransform = require('./codemods/global-api') - api.transformScript(api.entryFile, globalApiTransform) - - const resolveFile = (file) => { - if (!/\.(j|t)s$/ig.test(file)) { - file += '.js' - } - let resolvedPath = api.resolve(file) - const possiblePaths = [ - resolvedPath, - resolvedPath.replace(/\.js$/ig, '.ts'), - path.join(resolvedPath.replace(/\.js$/ig, ''), 'index.js'), - path.join(resolvedPath.replace(/\.js$/ig, ''), 'index.ts') - ] - for (const p of possiblePaths) { - if (fs.existsSync(p)) { - return path.relative(api.resolve('.'), p) + if (api.hasPlugin('unit-jest') || api.hasPlugin('unit-mocha')) { + api.extendPackage({ + devDependencies: { + '@vue/test-utils': '^2.0.0-alpha.1' } - } - } - - const routerPath = resolveFile('src/router') - console.log(routerPath) - if (routerPath) { - api.transformScript(routerPath, globalApiTransform) - api.transformScript(routerPath, require('./codemods/router')) - } + }) - const storePath = resolveFile('src/store') - if (storePath) { - api.transformScript(storePath, globalApiTransform) - api.transformScript(storePath, require('./codemods/vuex')) + api.exitLog('Installed @vue/test-utils 2.0.') + api.exitLog('Documentation available at https://github.com/vuejs/vue-test-utils-next') } } diff --git a/index.js b/index.js index 529eaf7..1b6c05d 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,14 @@ +const chalk = require('chalk') + module.exports = (api, options) => { + try { + api.assertVersion('< 4.5.0') + } catch (e) { + console.warn(chalk.red(`Vue CLI now supports Vue 3 by default.`)) + console.warn(chalk.red(`vue-cli-plugin-vue-next is no longer needed, please remove it from the dependencies.`)) + return + } + const vueLoaderCacheConfig = api.genCacheConfig('vue-loader', { 'vue-loader': require('vue-loader/package.json').version, '@vue/compiler-sfc': require('@vue/compiler-sfc/package.json').version @@ -10,7 +20,7 @@ module.exports = (api, options) => { 'vue$', options.runtimeCompiler ? 'vue/dist/vue.esm-bundler.js' - : '@vue/runtime-dom' + : 'vue/dist/vue.runtime.esm-bundler.js' ) config.module diff --git a/package.json b/package.json index 1121c15..186c62f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-cli-plugin-vue-next", - "version": "0.0.5", + "version": "0.1.4", "description": "Vue CLI plugin for trying out vue-next (experimental)", "main": "index.js", "repository": { @@ -13,6 +13,7 @@ "test": "jest" }, "dependencies": { + "chalk": "^4.1.0", "vue-loader": "^16.0.0-alpha.3" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index ae98141..671dfab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1455,6 +1455,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"