diff --git a/packages/@vue/cli/__tests__/Generator.spec.js b/packages/@vue/cli/__tests__/Generator.spec.js index 263aa4a792..405a56d161 100644 --- a/packages/@vue/cli/__tests__/Generator.spec.js +++ b/packages/@vue/cli/__tests__/Generator.spec.js @@ -256,7 +256,7 @@ test('api: extendPackage merge dependencies', async () => { }) test('api: warn invalid dep range', async () => { - new Generator('/', { plugins: [ + const generator = new Generator('/', { plugins: [ { id: 'test1', apply: api => { @@ -269,6 +269,8 @@ test('api: warn invalid dep range', async () => { } ] }) + await generator.generate() + expect(logs.warn.some(([msg]) => { return ( msg.match(/invalid version range for dependency "foo"/) && @@ -278,7 +280,7 @@ test('api: warn invalid dep range', async () => { }) test('api: extendPackage dependencies conflict', async () => { - new Generator('/', { plugins: [ + const generator = new Generator('/', { plugins: [ { id: 'test1', apply: api => { @@ -301,6 +303,8 @@ test('api: extendPackage dependencies conflict', async () => { } ] }) + await generator.generate() + expect(logs.warn.some(([msg]) => { return ( msg.match(/conflicting versions for project dependency "foo"/) && @@ -312,7 +316,7 @@ test('api: extendPackage dependencies conflict', async () => { }) test('api: extendPackage merge warn nonstrictly semver deps', async () => { - new Generator('/', { plugins: [ + const generator = new Generator('/', { plugins: [ { id: 'test3', apply: api => { @@ -335,6 +339,8 @@ test('api: extendPackage merge warn nonstrictly semver deps', async () => { } ] }) + await generator.generate() + expect(logs.warn.some(([msg]) => { return ( msg.match(/conflicting versions for project dependency "bar"/) && @@ -436,10 +442,10 @@ test('api: hasPlugin', () => { ] }) }) -test('api: onCreateComplete', () => { +test('api: onCreateComplete', async () => { const fn = () => {} const cbs = [] - new Generator('/', { + const generator = new Generator('/', { plugins: [ { id: 'test', @@ -467,6 +473,9 @@ test('api: afterInvoke', () => { ], afterInvokeCbs: cbs }) + + await generator.generate() + expect(cbs).toContain(fn) }) diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/index.js b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/index.js new file mode 100644 index 0000000000..22ba464be7 --- /dev/null +++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/index.js @@ -0,0 +1,17 @@ +const sleep = n => new Promise(resolve => setTimeout(resolve, n)) + +module.exports = async (api, options) => { + api.render('./template', options) + + // add asynchronous code test + await sleep(1000) + + api.extendPackage({ + scripts: { + testasync: 'this is the test' + }, + devDependencies: { + 'vue-cli-plugin-async-generator': 'v0.0.1' + } + }) +} diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/template/test.js b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/template/test.js new file mode 100644 index 0000000000..12db7fb421 --- /dev/null +++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/generator/template/test.js @@ -0,0 +1 @@ +<%= ok %> diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/preset.json b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/preset.json new file mode 100644 index 0000000000..63ec289edc --- /dev/null +++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/preset.json @@ -0,0 +1,5 @@ +{ + "plugins": { + "@vue/cli-plugin-babel": {} + } +} diff --git a/packages/@vue/cli/__tests__/mock-preset-with-async-generator/prompts.js b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/prompts.js new file mode 100644 index 0000000000..26971b45f2 --- /dev/null +++ b/packages/@vue/cli/__tests__/mock-preset-with-async-generator/prompts.js @@ -0,0 +1,5 @@ +module.exports = [{ + type: 'confirm', + name: 'ok', + message: 'Are you ok?' +}] diff --git a/packages/@vue/cli/__tests__/preset.spec.js b/packages/@vue/cli/__tests__/preset.spec.js index 7afab7b1f9..5329976922 100644 --- a/packages/@vue/cli/__tests__/preset.spec.js +++ b/packages/@vue/cli/__tests__/preset.spec.js @@ -56,3 +56,32 @@ test('should recognize generator/index.js in a local preset directory', async () const pkg = require(path.resolve(cwd, name, 'package.json')) expect(pkg.devDependencies).toHaveProperty('@vue/cli-plugin-babel') }) + +test('should recognize generator/index.js in a local preset directory by async generatory', async () => { + const cwd = path.resolve(__dirname, '../../../test') + const name = 'test-preset-template-async-generator' + + expectPrompts([{ + message: 'Are you ok', + confirm: true + }]) + + await create( + name, + { + force: true, + git: false, + cwd, + preset: path.resolve(__dirname, './mock-preset-with-async-generator') + } + ) + + const testFile = await fs.readFile(path.resolve(cwd, name, 'test.js'), 'utf-8') + expect(testFile).toBe('true\n') + + const pkg = require(path.resolve(cwd, name, 'package.json')) + expect(pkg.devDependencies).toHaveProperty('@vue/cli-plugin-babel') + expect(pkg.devDependencies).toHaveProperty('vue-cli-plugin-async-generator') + expect(pkg.scripts).toHaveProperty('testasync') +}) + diff --git a/packages/@vue/cli/lib/Generator.js b/packages/@vue/cli/lib/Generator.js index b99dcebd28..c449fba02b 100644 --- a/packages/@vue/cli/lib/Generator.js +++ b/packages/@vue/cli/lib/Generator.js @@ -83,7 +83,7 @@ module.exports = class Generator { this.pm = new PackageManager({ context }) this.imports = {} this.rootOptions = {} - this.afterInvokeCbs = [] + this.afterInvokeCbs = afterInvokeCbs this.afterAnyInvokeCbs = afterAnyInvokeCbs this.configTransforms = {} this.defaultConfigTransforms = defaultConfigTransforms @@ -98,10 +98,8 @@ module.exports = class Generator { // exit messages this.exitLogs = [] - const pluginIds = plugins.map(p => p.id) - // load all the other plugins - this.allPlugins = Object.keys(this.pkg.dependencies || {}) + this.allPluginIds = Object.keys(this.pkg.dependencies || {}) .concat(Object.keys(this.pkg.devDependencies || {})) .filter(isPlugin) @@ -110,43 +108,55 @@ module.exports = class Generator { ? cliService.options : inferRootOptions(pkg) + this.rootOptions = rootOptions + } + + async initPlugins () { + const { rootOptions, invoking } = this + const pluginIds = this.plugins.map(p => p.id) + // apply hooks from all plugins - this.allPlugins.forEach(id => { + for (const id of this.allPluginIds) { const api = new GeneratorAPI(id, this, {}, rootOptions) - const pluginGenerator = loadModule(`${id}/generator`, context) + const pluginGenerator = loadModule(`${id}/generator`, this.context) if (pluginGenerator && pluginGenerator.hooks) { - pluginGenerator.hooks(api, {}, rootOptions, pluginIds) + await pluginGenerator.hooks(api, {}, rootOptions, pluginIds) } - }) + } // We are doing save/load to make the hook order deterministic // save "any" hooks const afterAnyInvokeCbsFromPlugins = this.afterAnyInvokeCbs // reset hooks - this.afterInvokeCbs = afterInvokeCbs this.afterAnyInvokeCbs = [] this.postProcessFilesCbs = [] // apply generators from plugins - plugins.forEach(({ id, apply, options }) => { + for (const plugin of this.plugins) { + const { id, apply, options } = plugin const api = new GeneratorAPI(id, this, options, rootOptions) - apply(api, options, rootOptions, invoking) + await apply(api, options, rootOptions, invoking) if (apply.hooks) { - apply.hooks(api, options, rootOptions, pluginIds) + // while we execute the entire `hooks` function, + // only the `afterInvoke` hook is respected + // because `afterAnyHooks` is already determined by the `allPluginIds` loop aboe + await apply.hooks(api, options, rootOptions, pluginIds) } - }) - // load "any" hooks - this.afterAnyInvokeCbs = afterAnyInvokeCbsFromPlugins + // restore "any" hooks + this.afterAnyInvokeCbs = afterAnyInvokeCbsFromPlugins + } } async generate ({ extractConfigFiles = false, checkExisting = false } = {}) { + await this.initPlugins() + // save the file system before applying plugin for comparison const initialFiles = Object.assign({}, this.files) // extract configs from package.json into dedicated files. @@ -284,7 +294,7 @@ module.exports = class Generator { hasPlugin (_id, _version) { return [ ...this.plugins.map(p => p.id), - ...this.allPlugins + ...this.allPluginIds ].some(id => { if (!matchesPluginId(_id, id)) { return false