diff --git a/.eslintignore b/.eslintignore index ba322b37b4..08f4631091 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,6 @@ node_modules template +template-vue3 packages/test temp entry-wc.js diff --git a/package.json b/package.json index cf7a657079..acf5742a47 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,6 @@ }, "resolutions": { "puppeteer": "1.11.0", - "vue": "^2.6.11", "vue-template-compiler": "^2.6.11", "vue-server-renderer": "^2.6.11" } diff --git a/packages/@vue/babel-preset-app/README.md b/packages/@vue/babel-preset-app/README.md index 839a0cc405..aed82da6e5 100644 --- a/packages/@vue/babel-preset-app/README.md +++ b/packages/@vue/babel-preset-app/README.md @@ -88,7 +88,7 @@ Use this option when you have 3rd party dependencies that are not processed by B - Default: `true`. -Set to `false` to disable JSX support. Or you can toggle [@vue/babel-preset-jsx](https://github.com/vuejs/jsx/tree/dev/packages/babel-preset-jsx) features here. +Set to `false` to disable JSX support. Or you can toggle [@vue/babel-preset-jsx](https://github.com/vuejs/jsx/tree/dev/packages/babel-preset-jsx) (or [@ant-design-vue/babel-plugin-jsx](https://github.com/vueComponent/jsx) for Vue 3 projects) features here. ### loose diff --git a/packages/@vue/babel-preset-app/index.js b/packages/@vue/babel-preset-app/index.js index ab8722f75c..79fa8eadd8 100644 --- a/packages/@vue/babel-preset-app/index.js +++ b/packages/@vue/babel-preset-app/index.js @@ -112,7 +112,22 @@ module.exports = (context, options = {}) => { // JSX if (options.jsx !== false) { - presets.push([require('@vue/babel-preset-jsx'), typeof options.jsx === 'object' ? options.jsx : {}]) + let jsxOptions = {} + if (typeof options.jsx === 'object') { + jsxOptions = options.jsx + } + + let vueVersion = 2 + try { + const Vue = require('vue') + vueVersion = semver.major(Vue.version) + } catch (e) {} + + if (vueVersion === 2) { + presets.push([require('@vue/babel-preset-jsx'), jsxOptions]) + } else if (vueVersion === 3) { + plugins.push([require('@ant-design-vue/babel-plugin-jsx'), jsxOptions]) + } } const runtimePath = path.dirname(require.resolve('@babel/runtime/package.json')) diff --git a/packages/@vue/babel-preset-app/package.json b/packages/@vue/babel-preset-app/package.json index 7cd1999597..22003ebc1d 100644 --- a/packages/@vue/babel-preset-app/package.json +++ b/packages/@vue/babel-preset-app/package.json @@ -22,6 +22,7 @@ }, "homepage": "https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/babel-preset-app#readme", "dependencies": { + "@ant-design-vue/babel-plugin-jsx": "^1.0.0-0", "@babel/core": "^7.9.6", "@babel/helper-compilation-targets": "^7.9.6", "@babel/helper-module-imports": "^7.8.3", @@ -40,11 +41,15 @@ }, "peerDependencies": { "@babel/core": "*", - "core-js": "^3" + "core-js": "^3", + "vue": "^2 || ^3.0.0-0" }, "peerDependenciesMeta": { "core-js": { "optional": true + }, + "vue": { + "optional": true } } } diff --git a/packages/@vue/cli-plugin-babel/__tests__/babelMigrator.spec.js b/packages/@vue/cli-plugin-babel/__tests__/babelMigrator.spec.js index 42414a9c9a..697aa21d07 100644 --- a/packages/@vue/cli-plugin-babel/__tests__/babelMigrator.spec.js +++ b/packages/@vue/cli-plugin-babel/__tests__/babelMigrator.spec.js @@ -2,9 +2,6 @@ const create = require('@vue/cli-test-utils/createUpgradableProject') const { logs } = require('@vue/cli-shared-utils') jest.setTimeout(300000) -beforeEach(() => { - process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN = true -}) test('upgrade: plugin-babel v3.5', async () => { const project = await create('plugin-babel-legacy', { diff --git a/packages/@vue/cli-plugin-babel/__tests__/babel-runtime.spec.js b/packages/@vue/cli-plugin-babel/__tests__/babelRuntime.spec.js similarity index 100% rename from packages/@vue/cli-plugin-babel/__tests__/babel-runtime.spec.js rename to packages/@vue/cli-plugin-babel/__tests__/babelRuntime.spec.js diff --git a/packages/@vue/cli-plugin-babel/__tests__/transpileDependencies.spec.js b/packages/@vue/cli-plugin-babel/__tests__/transpileDependencies.spec.js index fcd392038d..3f3dba1ba1 100644 --- a/packages/@vue/cli-plugin-babel/__tests__/transpileDependencies.spec.js +++ b/packages/@vue/cli-plugin-babel/__tests__/transpileDependencies.spec.js @@ -63,6 +63,11 @@ beforeAll(async () => { ) }) +afterAll(async () => { + // avoid the non-existent made-up deps interfere with other tests + await project.rm('package.json') +}) + test('dep from node_modules should not been transpiled', async () => { await project.run('vue-cli-service build') expect(await readVendorFile()).toMatch('() => "__TEST__"') diff --git a/packages/@vue/cli-plugin-eslint/__tests__/eslintMigrator.spec.js b/packages/@vue/cli-plugin-eslint/__tests__/eslintMigrator.spec.js index 6cb2877a47..de39574006 100644 --- a/packages/@vue/cli-plugin-eslint/__tests__/eslintMigrator.spec.js +++ b/packages/@vue/cli-plugin-eslint/__tests__/eslintMigrator.spec.js @@ -1,10 +1,6 @@ jest.setTimeout(300000) jest.mock('inquirer') -beforeEach(() => { - process.env.VUE_CLI_TEST_DO_INSTALL_PLUGIN = true -}) - const create = require('@vue/cli-test-utils/createUpgradableProject') const { expectPrompts } = require('inquirer') diff --git a/packages/@vue/cli-plugin-eslint/__tests__/eslintVue3.spec.js b/packages/@vue/cli-plugin-eslint/__tests__/eslintVue3.spec.js new file mode 100644 index 0000000000..cefbd9eb68 --- /dev/null +++ b/packages/@vue/cli-plugin-eslint/__tests__/eslintVue3.spec.js @@ -0,0 +1,53 @@ +jest.setTimeout(300000) + +const generateWithPlugin = require('@vue/cli-test-utils/generateWithPlugin') +const createOutside = require('@vue/cli-test-utils/createUpgradableProject') + +test('Vue 3 base', async () => { + const { pkg } = await generateWithPlugin([ + { + id: '@vue/cli-service', + apply: require('@vue/cli-service/generator'), + options: { + vueVersion: '3' + } + }, + { + id: '@vue/cli-plugineslint', + apply: require('../generator'), + options: {} + } + ]) + + expect(pkg.scripts.lint).toBeTruthy() + expect(pkg.eslintConfig.extends).toEqual([ + 'plugin:vue/vue3-essential', 'eslint:recommended' + ]) +}) + +test('Should allow fragments in Vue 3 projects', async () => { + const { write, run } = await createOutside('eslint-vue3-fragment', { + vueVersion: '3', + plugins: { + '@vue/cli-plugin-eslint': {} + } + }) + await write('src/App.vue', ` + + +`) + + await run('vue-cli-service lint') +}) diff --git a/packages/@vue/cli-plugin-eslint/eslintDeps.js b/packages/@vue/cli-plugin-eslint/eslintDeps.js index 057908e885..ca5ce62387 100644 --- a/packages/@vue/cli-plugin-eslint/eslintDeps.js +++ b/packages/@vue/cli-plugin-eslint/eslintDeps.js @@ -28,9 +28,13 @@ const DEPS_MAP = { exports.DEPS_MAP = DEPS_MAP -exports.getDeps = function (api, preset) { +exports.getDeps = function (api, preset, rootOptions = {}) { const deps = Object.assign({}, DEPS_MAP.base, DEPS_MAP[preset]) + if (rootOptions.vueVersion === '3') { + Object.assign(deps, { 'eslint-plugin-vue': '^7.0.0-0' }) + } + if (api.hasPlugin('typescript')) { Object.assign(deps, DEPS_MAP.typescript) } diff --git a/packages/@vue/cli-plugin-eslint/eslintOptions.js b/packages/@vue/cli-plugin-eslint/eslintOptions.js index b3bce290cc..c6aea69c3b 100644 --- a/packages/@vue/cli-plugin-eslint/eslintOptions.js +++ b/packages/@vue/cli-plugin-eslint/eslintOptions.js @@ -1,4 +1,4 @@ -exports.config = (api, preset) => { +exports.config = (api, preset, rootOptions = {}) => { const config = { root: true, env: { node: true }, @@ -40,6 +40,15 @@ exports.config = (api, preset) => { } } + if (rootOptions.vueVersion === '3') { + const updateConfig = cfg => + cfg.replace( + /plugin:vue\/(essential|recommended|strongly-recommended)/gi, + 'plugin:vue/vue3-$1' + ) + config.extends = config.extends.map(updateConfig) + } + return config } diff --git a/packages/@vue/cli-plugin-eslint/generator/index.js b/packages/@vue/cli-plugin-eslint/generator/index.js index 042c4fdf56..d9d3694256 100644 --- a/packages/@vue/cli-plugin-eslint/generator/index.js +++ b/packages/@vue/cli-plugin-eslint/generator/index.js @@ -1,9 +1,9 @@ const fs = require('fs') const path = require('path') -module.exports = (api, { config, lintOn = [] }, _, invoking) => { - const eslintConfig = require('../eslintOptions').config(api, config) - const devDependencies = require('../eslintDeps').getDeps(api, config) +module.exports = (api, { config, lintOn = [] }, rootOptions, invoking) => { + const eslintConfig = require('../eslintOptions').config(api, config, rootOptions) + const devDependencies = require('../eslintDeps').getDeps(api, config, rootOptions) const pkg = { scripts: { diff --git a/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js b/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js index baac1337b6..2cf76c8207 100644 --- a/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js +++ b/packages/@vue/cli-plugin-router/__tests__/routerGenerator.spec.js @@ -62,3 +62,77 @@ test('use with Babel', async () => { expect(pkg.dependencies).toHaveProperty('vue-router') }) + +test('use with Vue 3', async () => { + const { files, pkg } = await generateWithPlugin([ + { + id: '@vue/cli-service', + apply: require('@vue/cli-service/generator'), + options: { + vueVersion: '3' + } + }, + { + id: '@vue/cli-plugin-router', + apply: require('../generator'), + options: {} + } + ]) + + expect(files['src/router/index.js']).toBeTruthy() + expect(files['src/router/index.js']).toMatch('createRouter') + expect(files['src/router/index.js']).toMatch('history: createWebHashHistory()') + + expect(files['src/main.js']).toMatch('.use(router)') + + expect(pkg.dependencies).toHaveProperty('vue-router') + expect(pkg.dependencies['vue-router']).toMatch('^4') +}) + +test('Vue 3 + History Mode', async () => { + const { files } = await generateWithPlugin([ + { + id: '@vue/cli-service', + apply: require('@vue/cli-service/generator'), + options: { + vueVersion: '3' + } + }, + { + id: '@vue/cli-plugin-router', + apply: require('../generator'), + options: { + historyMode: true + } + } + ]) + + expect(files['src/router/index.js']).toMatch(/import {.*createWebHistory/) + expect(files['src/router/index.js']).toMatch('history: createWebHistory(process.env.BASE_URL)') +}) + +test('Vue 3 + TypeScript', async () => { + const { files } = await generateWithPlugin([ + { + id: '@vue/cli-service', + apply: require('@vue/cli-service/generator'), + options: { + vueVersion: '3' + } + }, + { + id: '@vue/cli-plugin-router', + apply: require('../generator'), + options: {} + }, + { + id: '@vue/cli-plugin-typescript', + apply: require('@vue/cli-plugin-typescript/generator'), + options: {} + } + ]) + + expect(files['src/router/index.ts']).toBeTruthy() + expect(files['src/router/index.ts']).toMatch(/import {.*RouteRecordRaw/) + expect(files['src/router/index.ts']).toMatch('const routes: Array =') +}) diff --git a/packages/@vue/cli-plugin-router/generator/index.js b/packages/@vue/cli-plugin-router/generator/index.js index eecf8c1a5b..7f7bfb7ae7 100644 --- a/packages/@vue/cli-plugin-router/generator/index.js +++ b/packages/@vue/cli-plugin-router/generator/index.js @@ -1,12 +1,24 @@ -module.exports = (api, options = {}) => { +module.exports = (api, options = {}, rootOptions = {}) => { + const isVue3 = (rootOptions.vueVersion === '3') + api.injectImports(api.entryFile, `import router from './router'`) - api.injectRootOptions(api.entryFile, `router`) - api.extendPackage({ - dependencies: { - 'vue-router': '^3.2.0' - } - }) + if (isVue3) { + api.transformScript(api.entryFile, require('./injectUseRouter')) + api.extendPackage({ + dependencies: { + 'vue-router': '^4.0.0-0' + } + }) + } else { + api.injectRootOptions(api.entryFile, `router`) + + api.extendPackage({ + dependencies: { + 'vue-router': '^3.2.0' + } + }) + } api.render('./template', { historyMode: options.historyMode, @@ -14,6 +26,14 @@ module.exports = (api, options = {}) => { hasTypeScript: api.hasPlugin('typescript') }) + if (isVue3) { + api.render('./template-vue3', { + historyMode: options.historyMode, + doesCompile: api.hasPlugin('babel') || api.hasPlugin('typescript'), + hasTypeScript: api.hasPlugin('typescript') + }) + } + if (api.invoking) { if (api.hasPlugin('typescript')) { /* eslint-disable-next-line node/no-extraneous-require */ diff --git a/packages/@vue/cli-plugin-router/generator/injectUseRouter.js b/packages/@vue/cli-plugin-router/generator/injectUseRouter.js new file mode 100644 index 0000000000..5231512de2 --- /dev/null +++ b/packages/@vue/cli-plugin-router/generator/injectUseRouter.js @@ -0,0 +1,29 @@ +module.exports = (file, api) => { + const j = api.jscodeshift + const root = j(file.source) + + const appRoots = root.find(j.CallExpression, (node) => { + if (j.Identifier.check(node.callee) && node.callee.name === 'createApp') { + return true + } + + if ( + j.MemberExpression.check(node.callee) && + j.Identifier.check(node.callee.object) && + node.callee.object.name === 'Vue' && + j.Identifier.check(node.callee.property) && + node.callee.property.name === 'createApp' + ) { + return true + } + }) + + appRoots.replaceWith(({ node: createAppCall }) => { + return j.callExpression( + j.memberExpression(createAppCall, j.identifier('use')), + [j.identifier('router')] + ) + }) + + return root.toSource() +} diff --git a/packages/@vue/cli-plugin-router/generator/template-vue3/src/router/index.js b/packages/@vue/cli-plugin-router/generator/template-vue3/src/router/index.js new file mode 100644 index 0000000000..8678e20202 --- /dev/null +++ b/packages/@vue/cli-plugin-router/generator/template-vue3/src/router/index.js @@ -0,0 +1,45 @@ +import { createRouter<% + if (historyMode) { + %>, createWebHistory<% + } else { + %>, createWebHashHistory<% + } + + if (hasTypeScript) { + %>, RouteRecordRaw<% + } + %> } from 'vue-router' +import Home from '../views/Home.vue' + +const routes<% if (hasTypeScript) { %>: Array<% } %> = [ + { + path: '/', + name: 'Home', + component: Home + }, + { + path: '/about', + name: 'About', + // route level code-splitting + // this generates a separate chunk (about.[hash].js) for this route + // which is lazy-loaded when the route is visited. + <%_ if (doesCompile) { _%> + component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') + <%_ } else { _%> + component: function () { + return import(/* webpackChunkName: "about" */ '../views/About.vue') + } + <%_ } _%> + } +] + +const router = createRouter({ + <%_ if (historyMode) { _%> + history: createWebHistory(process.env.BASE_URL), + <%_ } else { _%> + history: createWebHashHistory(), + <%_ } _%> + routes +}) + +export default router diff --git a/packages/@vue/cli-plugin-typescript/__tests__/tsPlugin.helper.js b/packages/@vue/cli-plugin-typescript/__tests__/tsPlugin.helper.js index 51a5581c33..4584b8862a 100644 --- a/packages/@vue/cli-plugin-typescript/__tests__/tsPlugin.helper.js +++ b/packages/@vue/cli-plugin-typescript/__tests__/tsPlugin.helper.js @@ -4,15 +4,21 @@ const path = require('path') const portfinder = require('portfinder') const createServer = require('@vue/cli-test-utils/createServer') const create = require('@vue/cli-test-utils/createTestProject') +const createOutside = require('@vue/cli-test-utils/createUpgradableProject') const serve = require('@vue/cli-test-utils/serveWithPuppeteer') const launchPuppeteer = require('@vue/cli-test-utils/launchPuppeteer') -exports.assertServe = async (name, options) => { +exports.assertServe = async (name, options, outside = false) => { test('serve', async () => { - const project = await create(name, options) + let project + if (outside) { + project = await createOutside(name, options) + } else { + project = await create(name, options) + } await serve( - () => project.run('vue-cli-service serve'), + () => project.run('yarn serve'), async ({ page, nextUpdate, helpers }) => { const msg = `Welcome to Your Vue.js + TypeScript App` expect(await helpers.getText('h1')).toMatch(msg) @@ -40,12 +46,17 @@ exports.assertServe = async (name, options) => { }) } -exports.assertBuild = async (name, options, customAssert) => { +exports.assertBuild = async (name, options, customAssert, outside = false) => { let browser, server, page test('build', async () => { - const project = await create(name, options) + let project + if (outside) { + project = await createOutside(name, options) + } else { + project = await create(name, options) + } - const { stdout } = await project.run('vue-cli-service build') + const { stdout } = await project.run('yarn build') expect(stdout).toMatch('Build complete.') const port = await portfinder.getPortPromise() diff --git a/packages/@vue/cli-plugin-typescript/__tests__/tsPluginVue3.spec.js b/packages/@vue/cli-plugin-typescript/__tests__/tsPluginVue3.spec.js new file mode 100644 index 0000000000..b89dde464f --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/__tests__/tsPluginVue3.spec.js @@ -0,0 +1,13 @@ +jest.setTimeout(30000) + +const { assertServe, assertBuild } = require('./tsPlugin.helper') + +const options = { + vueVersion: '3', + plugins: { + '@vue/cli-plugin-typescript': {} + } +} + +assertServe('ts-vue-3-serve', options, true) +assertBuild('ts-vue-3-build', options, undefined, true) diff --git a/packages/@vue/cli-plugin-typescript/generator/index.js b/packages/@vue/cli-plugin-typescript/generator/index.js index 1e579b77e4..d9257efe47 100644 --- a/packages/@vue/cli-plugin-typescript/generator/index.js +++ b/packages/@vue/cli-plugin-typescript/generator/index.js @@ -6,10 +6,11 @@ module.exports = (api, { lintOn = [], convertJsToTs, allowJs -}, _, invoking) => { +}, rootOptions, invoking) => { if (typeof lintOn === 'string') { lintOn = lintOn.split(',') } + const isVue3 = rootOptions && rootOptions.vueVersion === '3' api.extendPackage({ devDependencies: { @@ -18,12 +19,20 @@ module.exports = (api, { }) if (classComponent) { - api.extendPackage({ - dependencies: { - 'vue-class-component': pluginDevDeps['vue-class-component'], - 'vue-property-decorator': pluginDevDeps['vue-property-decorator'] - } - }) + if (isVue3) { + api.extendPackage({ + dependencies: { + 'vue-class-component': '^8.0.0-0' + } + }) + } else { + api.extendPackage({ + dependencies: { + 'vue-class-component': pluginDevDeps['vue-class-component'], + 'vue-property-decorator': pluginDevDeps['vue-property-decorator'] + } + }) + } } if (tsLint) { @@ -81,10 +90,17 @@ module.exports = (api, { } api.render('./template', { - isTest: process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG, hasMocha: api.hasPlugin('unit-mocha'), hasJest: api.hasPlugin('unit-jest') }) + if (isVue3) { + api.render('./template-vue3') + + // In Vue 3, TSX interface is defined in https://github.com/vuejs/vue-next/blob/master/packages/runtime-dom/types/jsx.d.ts + // So no need to manually add a shim. + api.render((files) => delete files['src/shims-tsx.d.ts']) + } + require('./convert')(api, { tsLint, convertJsToTs }) } diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/App.vue b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/App.vue new file mode 100644 index 0000000000..1d7b8ac41f --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/App.vue @@ -0,0 +1,36 @@ +--- +extend: '@vue/cli-service/generator/template/src/App.vue' +replace: + - !!js/regexp /Welcome to Your Vue\.js App/g + - !!js/regexp / +<%# END_REPLACE %> diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/components/HelloWorld.vue b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/components/HelloWorld.vue new file mode 100644 index 0000000000..49f4011c10 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/components/HelloWorld.vue @@ -0,0 +1,27 @@ +--- +extend: '@vue/cli-service/generator/template/src/components/HelloWorld.vue' +replace: !!js/regexp / diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/shims-vue.d.ts b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/shims-vue.d.ts new file mode 100644 index 0000000000..32a1b5cd40 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/shims-vue.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import { defineComponent } from 'vue' + const component: ReturnType + export default component +} diff --git a/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/views/Home.vue b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/views/Home.vue new file mode 100644 index 0000000000..10729a6c17 --- /dev/null +++ b/packages/@vue/cli-plugin-typescript/generator/template-vue3/src/views/Home.vue @@ -0,0 +1,37 @@ +--- +extend: '@vue/cli-plugin-router/generator/template/src/views/Home.vue' +when: "rootOptions.plugins && rootOptions.plugins['@vue/cli-plugin-router']" +replace: + - !!js/regexp /Welcome to Your Vue\.js App/ + - !!js/regexp / +<%# END_REPLACE %> diff --git a/packages/@vue/cli-plugin-unit-jest/__tests__/jestPlugin.spec.js b/packages/@vue/cli-plugin-unit-jest/__tests__/jestPlugin.spec.js index 50548e7d42..618914c269 100644 --- a/packages/@vue/cli-plugin-unit-jest/__tests__/jestPlugin.spec.js +++ b/packages/@vue/cli-plugin-unit-jest/__tests__/jestPlugin.spec.js @@ -1,6 +1,7 @@ -jest.setTimeout(20000) +jest.setTimeout(300000) const create = require('@vue/cli-test-utils/createTestProject') +const createOutside = require('@vue/cli-test-utils/createUpgradableProject') test('should work', async () => { const project = await create('unit-jest', { @@ -125,3 +126,16 @@ test('should correctly configured eslint', async () => { }) await project.run(`vue-cli-service lint`) }) + +test('should work with Vue 3', async () => { + const project = await createOutside('unit-jest-vue-3', { + vueVersion: '3', + plugins: { + '@vue/cli-plugin-babel': {}, + '@vue/cli-plugin-unit-jest': {} + } + }) + const pkg = JSON.parse(await project.read('package.json')) + expect(pkg.devDependencies['@vue/test-utils']).toMatch('^2') + await project.run(`vue-cli-service test:unit`) +}) diff --git a/packages/@vue/cli-plugin-unit-jest/generator/index.js b/packages/@vue/cli-plugin-unit-jest/generator/index.js index be9dfd94ab..220189b9a9 100644 --- a/packages/@vue/cli-plugin-unit-jest/generator/index.js +++ b/packages/@vue/cli-plugin-unit-jest/generator/index.js @@ -1,5 +1,8 @@ -module.exports = (api, _, __, invoking) => { +module.exports = (api, options, rootOptions, invoking) => { + const isVue3 = rootOptions && rootOptions.vueVersion === '3' + api.render('./template', { + isVue3, hasTS: api.hasPlugin('typescript') }) @@ -8,7 +11,7 @@ module.exports = (api, _, __, invoking) => { 'test:unit': 'vue-cli-service test:unit' }, devDependencies: { - '@vue/test-utils': '^1.0.3' + '@vue/test-utils': isVue3 ? '^2.0.0-0' : '^1.0.3' }, jest: { preset: api.hasPlugin('babel') @@ -17,6 +20,21 @@ module.exports = (api, _, __, invoking) => { } }) + if (isVue3) { + api.extendPackage({ + devDependencies: { + 'vue-jest': '^5.0.0-0', + // vue-jest 5.0.0-alpha.1 requires typescript to be present + 'typescript': '~3.9.3' + }, + jest: { + transform: { + '^.+\\.vue$': 'vue-jest' + } + } + }) + } + if (api.hasPlugin('eslint')) { applyESLint(api) } diff --git a/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.js b/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.js index aa88537261..3851de59a3 100644 --- a/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.js +++ b/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.js @@ -7,7 +7,11 @@ describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = shallowMount(HelloWorld, { + <%_ if (isVue3) { _%> + props: { msg } + <%_ } else { _%> propsData: { msg } + <%_ } _%> }) expect(wrapper.text()).toMatch(msg) }) diff --git a/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.ts b/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.ts index 5d56d9ae54..962d8ed7b8 100644 --- a/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.ts +++ b/packages/@vue/cli-plugin-unit-jest/generator/template/tests/unit/example.spec.ts @@ -7,7 +7,11 @@ describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = shallowMount(HelloWorld, { + <%_ if (isVue3) { _%> + props: { msg } + <%_ } else { _%> propsData: { msg } + <%_ } _%> }) expect(wrapper.text()).toMatch(msg) }) diff --git a/packages/@vue/cli-plugin-unit-mocha/__tests__/mochaPlugin.spec.js b/packages/@vue/cli-plugin-unit-mocha/__tests__/mochaPlugin.spec.js index fdca6b364d..2fb088a8bd 100644 --- a/packages/@vue/cli-plugin-unit-mocha/__tests__/mochaPlugin.spec.js +++ b/packages/@vue/cli-plugin-unit-mocha/__tests__/mochaPlugin.spec.js @@ -1,6 +1,7 @@ -jest.setTimeout(20000) +jest.setTimeout(3000000) const create = require('@vue/cli-test-utils/createTestProject') +const createOutside = require('@vue/cli-test-utils/createUpgradableProject') test('should work', async () => { const project = await create('unit-mocha', { @@ -11,3 +12,30 @@ test('should work', async () => { }) await project.run(`vue-cli-service test:unit`) }) + +test('should work with Vue 3', async () => { + const project = await createOutside('unit-mocha-vue-3', { + vueVersion: '3', + plugins: { + '@vue/cli-plugin-babel': {}, + '@vue/cli-plugin-unit-mocha': {} + } + }) + const pkg = JSON.parse(await project.read('package.json')) + expect(pkg.devDependencies['@vue/test-utils']).toMatch('^2') + await project.run(`vue-cli-service test:unit`) +}) + +test('should work with Vue 3 + TS', async () => { + const project = await createOutside('unit-mocha-vue-3', { + vueVersion: '3', + plugins: { + '@vue/cli-plugin-babel': {}, + '@vue/cli-plugin-typescript': {}, + '@vue/cli-plugin-unit-mocha': {} + } + }) + const pkg = JSON.parse(await project.read('package.json')) + expect(pkg.devDependencies['@vue/test-utils']).toMatch('^2') + await project.run(`vue-cli-service test:unit`) +}) diff --git a/packages/@vue/cli-plugin-unit-mocha/generator/index.js b/packages/@vue/cli-plugin-unit-mocha/generator/index.js index 88fb63dfbf..cadf62d8f8 100644 --- a/packages/@vue/cli-plugin-unit-mocha/generator/index.js +++ b/packages/@vue/cli-plugin-unit-mocha/generator/index.js @@ -1,11 +1,14 @@ -module.exports = (api, _, __, invoking) => { +module.exports = (api, options, rootOptions, invoking) => { + const isVue3 = rootOptions && rootOptions.vueVersion === '3' + api.render('./template', { + isVue3, hasTS: api.hasPlugin('typescript') }) api.extendPackage({ devDependencies: { - '@vue/test-utils': '^1.0.3', + '@vue/test-utils': isVue3 ? '^2.0.0-0' : '^1.0.3', 'chai': '^4.1.2' }, scripts: { diff --git a/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.js b/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.js index 9231226366..12bbb8c152 100644 --- a/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.js +++ b/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.js @@ -8,7 +8,11 @@ describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = shallowMount(HelloWorld, { + <%_ if (isVue3) { _%> + props: { msg } + <%_ } else { _%> propsData: { msg } + <%_ } _%> }) expect(wrapper.text()).to.include(msg) }) diff --git a/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.ts b/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.ts index 16bb292f8d..b218903b4b 100644 --- a/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.ts +++ b/packages/@vue/cli-plugin-unit-mocha/generator/template/tests/unit/example.spec.ts @@ -8,7 +8,11 @@ describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = shallowMount(HelloWorld, { + <%_ if (isVue3) { _%> + props: { msg } + <%_ } else { _%> propsData: { msg } + <%_ } _%> }) expect(wrapper.text()).to.include(msg) }) diff --git a/packages/@vue/cli-plugin-unit-mocha/index.js b/packages/@vue/cli-plugin-unit-mocha/index.js index aa981d6b60..cfdec0aa5f 100644 --- a/packages/@vue/cli-plugin-unit-mocha/index.js +++ b/packages/@vue/cli-plugin-unit-mocha/index.js @@ -6,15 +6,22 @@ module.exports = api => { devtool: 'inline-cheap-module-source-map' }) + const { semver, loadModule } = require('@vue/cli-shared-utils') + const vue = loadModule('vue', api.service.context) + const isVue3 = (vue && semver.major(vue.version) === 3) + // when target === 'node', vue-loader will attempt to generate // SSR-optimized code. We need to turn that off here. - webpackConfig.module + // the `optimizeSSR` option is only available in vue-loader 15 + if (!isVue3) { + webpackConfig.module .rule('vue') .use('vue-loader') .tap(options => { options.optimizeSSR = false return options }) + } } }) diff --git a/packages/@vue/cli-plugin-vuex/__tests__/vuexGenerator.spec.js b/packages/@vue/cli-plugin-vuex/__tests__/vuexGenerator.spec.js index f95317c267..f7a81fc047 100644 --- a/packages/@vue/cli-plugin-vuex/__tests__/vuexGenerator.spec.js +++ b/packages/@vue/cli-plugin-vuex/__tests__/vuexGenerator.spec.js @@ -12,3 +12,27 @@ test('base', async () => { expect(pkg.dependencies).toHaveProperty('vuex') }) + +test('use with Vue 3', async () => { + const { files, pkg } = await generateWithPlugin([ + { + id: '@vue/cli-service', + apply: require('@vue/cli-service/generator'), + options: { + vueVersion: '3' + } + }, + { + id: 'vuex', + apply: require('../generator'), + options: {} + } + ]) + + expect(files['src/store/index.js']).toBeTruthy() + expect(files['src/store/index.js']).toMatch('import { createStore }') + expect(files['src/main.js']).toMatch('.use(store)') + + expect(pkg.dependencies).toHaveProperty('vuex') + expect(pkg.dependencies.vuex).toMatch('^4') +}) diff --git a/packages/@vue/cli-plugin-vuex/generator/index.js b/packages/@vue/cli-plugin-vuex/generator/index.js index 267a3d9fe9..80cf1ea02f 100644 --- a/packages/@vue/cli-plugin-vuex/generator/index.js +++ b/packages/@vue/cli-plugin-vuex/generator/index.js @@ -1,15 +1,25 @@ -module.exports = (api, options = {}) => { +module.exports = (api, options = {}, rootOptions = {}) => { api.injectImports(api.entryFile, `import store from './store'`) - api.injectRootOptions(api.entryFile, `store`) - api.extendPackage({ - dependencies: { - vuex: '^3.4.0' - } - }) + if (rootOptions.vueVersion === '3') { + api.transformScript(api.entryFile, require('./injectUseStore')) + api.extendPackage({ + dependencies: { + vuex: '^4.0.0-0' + } + }) + api.render('./template-vue3', {}) + } else { + api.injectRootOptions(api.entryFile, `store`) - api.render('./template', { - }) + api.extendPackage({ + dependencies: { + vuex: '^3.4.0' + } + }) + + api.render('./template', {}) + } if (api.invoking && api.hasPlugin('typescript')) { /* eslint-disable-next-line node/no-extraneous-require */ diff --git a/packages/@vue/cli-plugin-vuex/generator/injectUseStore.js b/packages/@vue/cli-plugin-vuex/generator/injectUseStore.js new file mode 100644 index 0000000000..41104e9510 --- /dev/null +++ b/packages/@vue/cli-plugin-vuex/generator/injectUseStore.js @@ -0,0 +1,29 @@ +module.exports = (file, api) => { + const j = api.jscodeshift + const root = j(file.source) + + const appRoots = root.find(j.CallExpression, (node) => { + if (j.Identifier.check(node.callee) && node.callee.name === 'createApp') { + return true + } + + if ( + j.MemberExpression.check(node.callee) && + j.Identifier.check(node.callee.object) && + node.callee.object.name === 'Vue' && + j.Identifier.check(node.callee.property) && + node.callee.property.name === 'createApp' + ) { + return true + } + }) + + appRoots.replaceWith(({ node: createAppCall }) => { + return j.callExpression( + j.memberExpression(createAppCall, j.identifier('use')), + [j.identifier('store')] + ) + }) + + return root.toSource() +} diff --git a/packages/@vue/cli-plugin-vuex/generator/template-vue3/src/store/index.js b/packages/@vue/cli-plugin-vuex/generator/template-vue3/src/store/index.js new file mode 100644 index 0000000000..5f05f19391 --- /dev/null +++ b/packages/@vue/cli-plugin-vuex/generator/template-vue3/src/store/index.js @@ -0,0 +1,12 @@ +import { createStore } from 'vuex' + +export default createStore({ + state: { + }, + mutations: { + }, + actions: { + }, + modules: { + } +}) diff --git a/packages/@vue/cli-service/__tests__/generator.spec.js b/packages/@vue/cli-service/__tests__/generator.spec.js index 47537fc132..52d8bb082a 100644 --- a/packages/@vue/cli-service/__tests__/generator.spec.js +++ b/packages/@vue/cli-service/__tests__/generator.spec.js @@ -1,46 +1,49 @@ const generateWithPlugin = require('@vue/cli-test-utils/generateWithPlugin') -test('sass (default)', async () => { - const { pkg, files } = await generateWithPlugin([ +function generateWithOptions (options) { + return generateWithPlugin([ { id: '@vue/cli-service', apply: require('../generator'), - options: { - cssPreprocessor: 'sass' - } + options } ]) +} + +test('sass (default)', async () => { + const { pkg, files } = await await generateWithOptions({ + cssPreprocessor: 'sass' + }) expect(files['src/App.vue']).toMatch('