Skip to content

Commit

Permalink
feat: make injectImports & injectRootOptions work for .vue files (
Browse files Browse the repository at this point in the history
#4168)

closes #1702
  • Loading branch information
sodatea committed Jun 21, 2019
1 parent 76e7c38 commit af25ef7
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 70 deletions.
32 changes: 32 additions & 0 deletions packages/@vue/cli/__tests__/Generator.spec.js
Expand Up @@ -23,6 +23,19 @@ new Vue({
}).$mount('#app')
`.trim())
fs.writeFileSync(path.resolve(templateDir, 'empty-entry.js'), `;`)
fs.writeFileSync(path.resolve(templateDir, 'hello.vue'), `
<template>
<p>Hello, {{ msg }}</p>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
`)

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

test('api: injectImport for .vue files', async () => {
const generator = new Generator('/', { plugins: [
{
id: 'test',
apply: api => {
api.injectImports('hello.vue', `import foo from 'foo'`)
api.render({
'hello.vue': path.join(templateDir, 'hello.vue')
})
}
}
] })

await generator.generate()
const content = fs.readFileSync('/hello.vue', 'utf-8')
expect(content).toMatch(/import foo from 'foo'/)
expect(content).toMatch(/<template>([\s\S]*)<\/template>/)
})

test('api: addEntryDuplicateInjection', async () => {
const generator = new Generator('/', { plugins: [
{
Expand Down
26 changes: 20 additions & 6 deletions packages/@vue/cli/lib/Generator.js
Expand Up @@ -5,7 +5,7 @@ const sortObject = require('./util/sortObject')
const writeFileTree = require('./util/writeFileTree')
const inferRootOptions = require('./util/inferRootOptions')
const normalizeFilePaths = require('./util/normalizeFilePaths')
const injectImportsAndOptions = require('./util/injectImportsAndOptions')
const runCodemod = require('./util/runCodemod')
const { toShortPluginId, matchesPluginId } = require('@vue/cli-shared-utils')
const ConfigTransform = require('./ConfigTransform')

Expand Down Expand Up @@ -215,11 +215,25 @@ module.exports = class Generator {

// handle imports and root option injections
Object.keys(files).forEach(file => {
files[file] = injectImportsAndOptions(
files[file],
this.imports[file],
this.rootOptions[file]
)
let imports = this.imports[file]
imports = imports instanceof Set ? Array.from(imports) : imports
if (imports && imports.length > 0) {
files[file] = runCodemod(
require('./util/codemods/injectImports'),
{ path: file, source: files[file] },
{ imports }
)
}

let injections = this.rootOptions[file]
injections = injections instanceof Set ? Array.from(injections) : injections
if (injections && injections.length > 0) {
files[file] = runCodemod(
require('./util/codemods/injectOptions'),
{ path: file, source: files[file] },
{ injections }
)
}
})

for (const postProcess of this.postProcessFilesCbs) {
Expand Down
29 changes: 29 additions & 0 deletions packages/@vue/cli/lib/util/codemods/injectImports.js
@@ -0,0 +1,29 @@
module.exports = function injectImports (fileInfo, api, { imports }) {
const j = api.jscodeshift
const root = j(fileInfo.source)

const toImportAST = i => j(`${i}\n`).nodes()[0].program.body[0]
const toImportHash = node => JSON.stringify({
specifiers: node.specifiers.map(s => s.local.name),
source: node.source.raw
})

const declarations = root.find(j.ImportDeclaration)
const importSet = new Set(declarations.nodes().map(toImportHash))
const nonDuplicates = node => !importSet.has(toImportHash(node))

const importASTNodes = imports.map(toImportAST).filter(nonDuplicates)

if (declarations.length) {
declarations
.at(-1)
// a tricky way to avoid blank line after the previous import
.forEach(({ node }) => delete node.loc)
.insertAfter(importASTNodes)
} else {
// no pre-existing import declarations
root.get().node.program.body.unshift(...importASTNodes)
}

return root.toSource()
}
27 changes: 27 additions & 0 deletions packages/@vue/cli/lib/util/codemods/injectOptions.js
@@ -0,0 +1,27 @@
module.exports = function injectOptions (fileInfo, api, { injections }) {
const j = api.jscodeshift
const root = j(fileInfo.source)

const toPropertyAST = i => {
return j(`({${i}})`).nodes()[0].program.body[0].expression.properties[0]
}

const properties = root
.find(j.NewExpression, {
callee: { name: 'Vue' },
arguments: [{ type: 'ObjectExpression' }]
})
.map(path => path.get('arguments', 0))
.get()
.node
.properties

const toPropertyHash = p => `${p.key.name}: ${j(p.value).toSource()}`
const propertySet = new Set(properties.map(toPropertyHash))
const nonDuplicates = p => !propertySet.has(toPropertyHash(p))

// inject at index length - 1 as it's usually the render fn
properties.splice(-1, 0, ...injections.map(toPropertyAST).filter(nonDuplicates))

return root.toSource()
}
64 changes: 0 additions & 64 deletions packages/@vue/cli/lib/util/injectImportsAndOptions.js

This file was deleted.

6 changes: 6 additions & 0 deletions packages/@vue/cli/lib/util/runCodemod.js
@@ -0,0 +1,6 @@
const jscodeshift = require('jscodeshift')
const adapt = require('vue-jscodeshift-adapter')

module.exports = function runCodemod (transform, fileInfo, options) {
return adapt(transform)(fileInfo, { jscodeshift }, options || {})
}
1 change: 1 addition & 0 deletions packages/@vue/cli/package.json
Expand Up @@ -56,6 +56,7 @@
"shortid": "^2.2.11",
"slash": "^3.0.0",
"validate-npm-package-name": "^3.0.0",
"vue-jscodeshift-adapter": "2.0.2",
"yaml-front-matter": "^3.4.1"
},
"engines": {
Expand Down

0 comments on commit af25ef7

Please sign in to comment.