feat: make injectImports & injectRootOptions work for .vue files (#4168)

closes #1702
This commit is contained in:
Haoqun Jiang
2019-06-21 17:20:53 +08:00
committed by GitHub
parent 76e7c38fc8
commit af25ef75ce
7 changed files with 115 additions and 70 deletions

View File

@@ -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'), `
@@ -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: [
{

View File

@@ -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')
@@ -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) {

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -1,64 +0,0 @@
module.exports = function injectImportsAndOptions (source, imports, injections) {
imports = imports instanceof Set ? Array.from(imports) : imports
injections = injections instanceof Set ? Array.from(injections) : injections
const hasImports = imports && imports.length > 0
const hasInjections = injections && injections.length > 0
if (!hasImports && !hasInjections) {
return source
}
const j = require('jscodeshift')
const root = j(source)
if (hasImports) {
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)
}
}
if (hasInjections) {
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()
}

View File

@@ -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 || {})
}

View File

@@ -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": {