Skip to content

Commit 8eb7fc3

Browse files
pksunkaraGuillaume Chau
authored andcommitted
fix: Don't allow duplicate injections of import statements and root options by plugins (vuejs#1774)
* feat(cli): Don't allow duplicate api.injectImports * feat(cli): Don't allow duplicate api.injectRootOptions * chore(cli): Added tests for duplicate injections
1 parent 9f0bf08 commit 8eb7fc3

File tree

2 files changed

+77
-6
lines changed

2 files changed

+77
-6
lines changed

packages/@vue/cli/__tests__/Generator.spec.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ fs.writeFileSync(path.resolve(templateDir, 'entry.js'), `
1616
import foo from 'foo'
1717
1818
new Vue({
19+
p: p(),
20+
baz,
1921
render: h => h(App)
2022
}).$mount('#app')
2123
`.trim())
@@ -454,7 +456,58 @@ test('api: addEntryImport & addEntryInjection', async () => {
454456

455457
await generator.generate()
456458
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/import foo from 'foo'\s+import bar from 'bar'/)
457-
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/new Vue\({\s+foo,\s+bar,\s+render: h => h\(App\)\s+}\)/)
459+
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/new Vue\({\s+p: p\(\),\s+baz,\s+foo,\s+bar,\s+render: h => h\(App\)\s+}\)/)
460+
})
461+
462+
test('api: addEntryDuplicateImport', async () => {
463+
const generator = new Generator('/', { plugins: [
464+
{
465+
id: 'test',
466+
apply: api => {
467+
api.injectImports('main.js', `import foo from 'foo'`)
468+
api.render({
469+
'main.js': path.join(templateDir, 'entry.js')
470+
})
471+
}
472+
}
473+
] })
474+
475+
await generator.generate()
476+
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/^import foo from 'foo'\s+new Vue/)
477+
})
478+
479+
test('api: addEntryDuplicateInjection', async () => {
480+
const generator = new Generator('/', { plugins: [
481+
{
482+
id: 'test',
483+
apply: api => {
484+
api.injectRootOptions('main.js', 'baz')
485+
api.render({
486+
'main.js': path.join(templateDir, 'entry.js')
487+
})
488+
}
489+
}
490+
] })
491+
492+
await generator.generate()
493+
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/{\s+p: p\(\),\s+baz,\s+render/)
494+
})
495+
496+
test('api: addEntryDuplicateNonIdentifierInjection', async () => {
497+
const generator = new Generator('/', { plugins: [
498+
{
499+
id: 'test',
500+
apply: api => {
501+
api.injectRootOptions('main.js', 'p: p()')
502+
api.render({
503+
'main.js': path.join(templateDir, 'entry.js')
504+
})
505+
}
506+
}
507+
] })
508+
509+
await generator.generate()
510+
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/{\s+p: p\(\),\s+baz,\s+render/)
458511
})
459512

460513
test('extract config files', async () => {

packages/@vue/cli/lib/util/injectImportsAndOptions.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,30 @@ module.exports = function injectImportsAndOptions (source, imports, injections)
1414

1515
if (hasImports) {
1616
const toImport = i => recast.parse(`${i}\n`).program.body[0]
17+
const importDeclarations = []
1718
let lastImportIndex = -1
19+
1820
recast.types.visit(ast, {
1921
visitImportDeclaration ({ node }) {
2022
lastImportIndex = ast.program.body.findIndex(n => n === node)
23+
importDeclarations.push(node)
2124
return false
2225
}
2326
})
2427
// avoid blank line after the previous import
2528
delete ast.program.body[lastImportIndex].loc
2629

27-
const newImports = imports.map(toImport)
30+
const nonDuplicates = i => {
31+
return !importDeclarations.some(node => {
32+
const result = node.source.raw === i.source.raw && node.specifiers.length === i.specifiers.length
33+
34+
return result && node.specifiers.every((item, index) => {
35+
return i.specifiers[index].local.name === item.local.name
36+
})
37+
})
38+
}
39+
40+
const newImports = imports.map(toImport).filter(nonDuplicates)
2841
ast.program.body.splice(lastImportIndex + 1, 0, ...newImports)
2942
}
3043

@@ -37,12 +50,17 @@ module.exports = function injectImportsAndOptions (source, imports, injections)
3750
if (node.callee.name === 'Vue') {
3851
const options = node.arguments[0]
3952
if (options && options.type === 'ObjectExpression') {
40-
const props = options.properties
53+
const nonDuplicates = i => {
54+
return !options.properties.slice(0, -1).some(p => {
55+
return p.key.name === i[0].key.name &&
56+
recast.print(p.value).code === recast.print(i[0].value).code
57+
})
58+
}
4159
// inject at index length - 1 as it's usually the render fn
4260
options.properties = [
43-
...props.slice(0, props.length - 1),
44-
...([].concat(...injections.map(toProperty))),
45-
...props.slice(props.length - 1)
61+
...options.properties.slice(0, -1),
62+
...([].concat(...injections.map(toProperty).filter(nonDuplicates))),
63+
...options.properties.slice(-1)
4664
]
4765
}
4866
}

0 commit comments

Comments
 (0)