Skip to content

Commit af25ef7

Browse files
authored
feat: make injectImports & injectRootOptions work for .vue files (#4168)
closes #1702
1 parent 76e7c38 commit af25ef7

File tree

7 files changed

+115
-70
lines changed

7 files changed

+115
-70
lines changed

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ new Vue({
2323
}).$mount('#app')
2424
`.trim())
2525
fs.writeFileSync(path.resolve(templateDir, 'empty-entry.js'), `;`)
26+
fs.writeFileSync(path.resolve(templateDir, 'hello.vue'), `
27+
<template>
28+
<p>Hello, {{ msg }}</p>
29+
</template>
30+
<script>
31+
export default {
32+
name: 'HelloWorld',
33+
props: {
34+
msg: String
35+
}
36+
}
37+
</script>
38+
`)
2639

2740
// replace stubs
2841
fs.writeFileSync(path.resolve(templateDir, 'replace.js'), `
@@ -505,6 +518,25 @@ test('api: addEntryDuplicateImport', async () => {
505518
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/^import foo from 'foo'\s+new Vue/)
506519
})
507520

521+
test('api: injectImport for .vue files', async () => {
522+
const generator = new Generator('/', { plugins: [
523+
{
524+
id: 'test',
525+
apply: api => {
526+
api.injectImports('hello.vue', `import foo from 'foo'`)
527+
api.render({
528+
'hello.vue': path.join(templateDir, 'hello.vue')
529+
})
530+
}
531+
}
532+
] })
533+
534+
await generator.generate()
535+
const content = fs.readFileSync('/hello.vue', 'utf-8')
536+
expect(content).toMatch(/import foo from 'foo'/)
537+
expect(content).toMatch(/<template>([\s\S]*)<\/template>/)
538+
})
539+
508540
test('api: addEntryDuplicateInjection', async () => {
509541
const generator = new Generator('/', { plugins: [
510542
{

packages/@vue/cli/lib/Generator.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const sortObject = require('./util/sortObject')
55
const writeFileTree = require('./util/writeFileTree')
66
const inferRootOptions = require('./util/inferRootOptions')
77
const normalizeFilePaths = require('./util/normalizeFilePaths')
8-
const injectImportsAndOptions = require('./util/injectImportsAndOptions')
8+
const runCodemod = require('./util/runCodemod')
99
const { toShortPluginId, matchesPluginId } = require('@vue/cli-shared-utils')
1010
const ConfigTransform = require('./ConfigTransform')
1111

@@ -215,11 +215,25 @@ module.exports = class Generator {
215215

216216
// handle imports and root option injections
217217
Object.keys(files).forEach(file => {
218-
files[file] = injectImportsAndOptions(
219-
files[file],
220-
this.imports[file],
221-
this.rootOptions[file]
222-
)
218+
let imports = this.imports[file]
219+
imports = imports instanceof Set ? Array.from(imports) : imports
220+
if (imports && imports.length > 0) {
221+
files[file] = runCodemod(
222+
require('./util/codemods/injectImports'),
223+
{ path: file, source: files[file] },
224+
{ imports }
225+
)
226+
}
227+
228+
let injections = this.rootOptions[file]
229+
injections = injections instanceof Set ? Array.from(injections) : injections
230+
if (injections && injections.length > 0) {
231+
files[file] = runCodemod(
232+
require('./util/codemods/injectOptions'),
233+
{ path: file, source: files[file] },
234+
{ injections }
235+
)
236+
}
223237
})
224238

225239
for (const postProcess of this.postProcessFilesCbs) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module.exports = function injectImports (fileInfo, api, { imports }) {
2+
const j = api.jscodeshift
3+
const root = j(fileInfo.source)
4+
5+
const toImportAST = i => j(`${i}\n`).nodes()[0].program.body[0]
6+
const toImportHash = node => JSON.stringify({
7+
specifiers: node.specifiers.map(s => s.local.name),
8+
source: node.source.raw
9+
})
10+
11+
const declarations = root.find(j.ImportDeclaration)
12+
const importSet = new Set(declarations.nodes().map(toImportHash))
13+
const nonDuplicates = node => !importSet.has(toImportHash(node))
14+
15+
const importASTNodes = imports.map(toImportAST).filter(nonDuplicates)
16+
17+
if (declarations.length) {
18+
declarations
19+
.at(-1)
20+
// a tricky way to avoid blank line after the previous import
21+
.forEach(({ node }) => delete node.loc)
22+
.insertAfter(importASTNodes)
23+
} else {
24+
// no pre-existing import declarations
25+
root.get().node.program.body.unshift(...importASTNodes)
26+
}
27+
28+
return root.toSource()
29+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module.exports = function injectOptions (fileInfo, api, { injections }) {
2+
const j = api.jscodeshift
3+
const root = j(fileInfo.source)
4+
5+
const toPropertyAST = i => {
6+
return j(`({${i}})`).nodes()[0].program.body[0].expression.properties[0]
7+
}
8+
9+
const properties = root
10+
.find(j.NewExpression, {
11+
callee: { name: 'Vue' },
12+
arguments: [{ type: 'ObjectExpression' }]
13+
})
14+
.map(path => path.get('arguments', 0))
15+
.get()
16+
.node
17+
.properties
18+
19+
const toPropertyHash = p => `${p.key.name}: ${j(p.value).toSource()}`
20+
const propertySet = new Set(properties.map(toPropertyHash))
21+
const nonDuplicates = p => !propertySet.has(toPropertyHash(p))
22+
23+
// inject at index length - 1 as it's usually the render fn
24+
properties.splice(-1, 0, ...injections.map(toPropertyAST).filter(nonDuplicates))
25+
26+
return root.toSource()
27+
}

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

Lines changed: 0 additions & 64 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const jscodeshift = require('jscodeshift')
2+
const adapt = require('vue-jscodeshift-adapter')
3+
4+
module.exports = function runCodemod (transform, fileInfo, options) {
5+
return adapt(transform)(fileInfo, { jscodeshift }, options || {})
6+
}

packages/@vue/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"shortid": "^2.2.11",
5757
"slash": "^3.0.0",
5858
"validate-npm-package-name": "^3.0.0",
59+
"vue-jscodeshift-adapter": "2.0.2",
5960
"yaml-front-matter": "^3.4.1"
6061
},
6162
"engines": {

0 commit comments

Comments
 (0)