diff --git a/packages/@vue/cli/__tests__/Generator.spec.js b/packages/@vue/cli/__tests__/Generator.spec.js index 8e169b3e43..0c48acb457 100644 --- a/packages/@vue/cli/__tests__/Generator.spec.js +++ b/packages/@vue/cli/__tests__/Generator.spec.js @@ -885,6 +885,52 @@ test('api: addConfigTransform transform vue warn', async () => { })).toBe(true) }) +test('avoid overwriting files that have not been modified', async () => { + const generator = new Generator('/', { + plugins: [ + { + id: 'test1', + apply: (api, options) => { + api.render((files, render) => { + files['foo.js'] = render('foo()') + }) + } + } + ], + files: { + // skip writing to this file + 'existFile.js': 'existFile()' + } + }) + + await generator.generate() + + expect(fs.readFileSync('/foo.js', 'utf-8')).toMatch('foo()') + expect(fs.existsSync('/existFile.js')).toBe(false) +}) + +test('overwrite files that have been modified', async () => { + const generator = new Generator('/', { + plugins: [ + { + id: 'test1', + apply: (api, options) => { + api.render((files, render) => { + files['existFile.js'] = render('foo()') + }) + } + } + ], + files: { + 'existFile.js': 'existFile()' + } + }) + + await generator.generate() + + expect(fs.readFileSync('/existFile.js', 'utf-8')).toMatch('foo()') +}) + test('extract config files', async () => { const configs = { vue: { diff --git a/packages/@vue/cli/lib/Generator.js b/packages/@vue/cli/lib/Generator.js index 929998ee18..162eca39a3 100644 --- a/packages/@vue/cli/lib/Generator.js +++ b/packages/@vue/cli/lib/Generator.js @@ -74,6 +74,24 @@ const ensureEOL = str => { return str } +/** + * Collect created/modified files into set + * @param {Record} files + * @param {Set} set + */ +const watchFiles = (files, set) => { + return new Proxy(files, { + set (target, key, value, receiver) { + set.add(key) + return Reflect.set(target, key, value, receiver) + }, + deleteProperty (target, key) { + set.delete(key) + return Reflect.deleteProperty(target, key) + } + }) +} + module.exports = class Generator { constructor (context, { pkg = {}, @@ -101,7 +119,11 @@ module.exports = class Generator { // for conflict resolution this.depSources = {} // virtual file tree - this.files = files + this.files = Object.keys(files).length + // when execute `vue add/invoke`, only created/modified files are written to disk + ? watchFiles(files, this.filesModifyRecord = new Set()) + // all files need to be written to disk + : files this.fileMiddlewares = [] this.postProcessFilesCbs = [] // exit messages @@ -177,7 +199,7 @@ module.exports = class Generator { this.sortPkg() this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n' // write/update file tree to disk - await writeFileTree(this.context, this.files, initialFiles) + await writeFileTree(this.context, this.files, initialFiles, this.filesModifyRecord) } extractConfigFiles (extractAll, checkExisting) { diff --git a/packages/@vue/cli/lib/util/writeFileTree.js b/packages/@vue/cli/lib/util/writeFileTree.js index 5eff5acde0..4d06449c5e 100644 --- a/packages/@vue/cli/lib/util/writeFileTree.js +++ b/packages/@vue/cli/lib/util/writeFileTree.js @@ -12,7 +12,14 @@ function deleteRemovedFiles (directory, newFiles, previousFiles) { })) } -module.exports = async function writeFileTree (dir, files, previousFiles) { +/** + * + * @param {string} dir + * @param {Record} files + * @param {Record} [previousFiles] + * @param {Set} [include] + */ +module.exports = async function writeFileTree (dir, files, previousFiles, include) { if (process.env.VUE_CLI_SKIP_WRITE) { return } @@ -20,6 +27,7 @@ module.exports = async function writeFileTree (dir, files, previousFiles) { await deleteRemovedFiles(dir, files, previousFiles) } Object.keys(files).forEach((name) => { + if (include && !include.has(name)) return const filePath = path.join(dir, name) fs.ensureDirSync(path.dirname(filePath)) fs.writeFileSync(filePath, files[name])