Skip to content

Commit e393be7

Browse files
ascendancyyAkryum
authored andcommitted
feat(generator): allow plugins to modify how configs are extracted (#1130)
* feat(generator): allow plugins to modify how configs are extracted * refactor(cli): change addConfigTransform parameters Allow plugin author to set config 'descriptions' instead of implementing their own transform functions. * fix(cli): fix missed issues from changing types from array to set * fix: use reserved config transforms to check in API * fix: lines dedupe
1 parent 8eb7fc3 commit e393be7

File tree

7 files changed

+288
-96
lines changed

7 files changed

+288
-96
lines changed

packages/@vue/cli-plugin-eslint/ui.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = api => {
1111
eslint: {
1212
js: ['.eslintrc.js'],
1313
json: ['.eslintrc', '.eslintrc.json'],
14+
yaml: ['.eslintrc.yaml', '.eslintrc.yml'],
1415
package: 'eslintConfig'
1516
},
1617
vue: {

packages/@vue/cli-plugin-pwa/lib/HtmlPwaPlugin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module.exports = class HtmlPwaPlugin {
2222
constructor (options = {}) {
2323
const iconPaths = Object.assign({}, defaultIconPaths, options.iconPaths)
2424
delete options.iconPaths
25-
this.options = Object.assign({iconPaths: iconPaths}, defaults, options)
25+
this.options = Object.assign({ iconPaths: iconPaths }, defaults, options)
2626
}
2727

2828
apply (compiler) {

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,98 @@ test('api: addEntryDuplicateNonIdentifierInjection', async () => {
510510
expect(fs.readFileSync('/main.js', 'utf-8')).toMatch(/{\s+p: p\(\),\s+baz,\s+render/)
511511
})
512512

513+
test('api: addConfigTransform', async () => {
514+
const configs = {
515+
fooConfig: {
516+
bar: 42
517+
}
518+
}
519+
520+
const generator = new Generator('/', { plugins: [
521+
{
522+
id: 'test',
523+
apply: api => {
524+
api.addConfigTransform('fooConfig', {
525+
file: {
526+
json: ['foo.config.json']
527+
}
528+
})
529+
api.extendPackage(configs)
530+
}
531+
}
532+
] })
533+
534+
await generator.generate({
535+
extractConfigFiles: true
536+
})
537+
538+
const json = v => JSON.stringify(v, null, 2)
539+
expect(fs.readFileSync('/foo.config.json', 'utf-8')).toMatch(json(configs.fooConfig))
540+
expect(generator.pkg).not.toHaveProperty('fooConfig')
541+
})
542+
543+
test('api: addConfigTransform (multiple)', async () => {
544+
const configs = {
545+
bazConfig: {
546+
field: 2501
547+
}
548+
}
549+
550+
const generator = new Generator('/', { plugins: [
551+
{
552+
id: 'test',
553+
apply: api => {
554+
api.addConfigTransform('bazConfig', {
555+
file: {
556+
js: ['.bazrc.js'],
557+
json: ['.bazrc', 'baz.config.json']
558+
}
559+
})
560+
api.extendPackage(configs)
561+
}
562+
}
563+
] })
564+
565+
await generator.generate({
566+
extractConfigFiles: true
567+
})
568+
569+
const js = v => `module.exports = ${stringifyJS(v, null, 2)}`
570+
expect(fs.readFileSync('/.bazrc.js', 'utf-8')).toMatch(js(configs.bazConfig))
571+
expect(generator.pkg).not.toHaveProperty('bazConfig')
572+
})
573+
574+
test('api: addConfigTransform transform vue warn', async () => {
575+
const configs = {
576+
vue: {
577+
lintOnSave: true
578+
}
579+
}
580+
581+
const generator = new Generator('/', { plugins: [
582+
{
583+
id: 'test',
584+
apply: api => {
585+
api.addConfigTransform('vue', {
586+
file: {
587+
js: ['vue.config.js']
588+
}
589+
})
590+
api.extendPackage(configs)
591+
}
592+
}
593+
] })
594+
595+
await generator.generate({
596+
extractConfigFiles: true
597+
})
598+
599+
expect(fs.readFileSync('/vue.config.js', 'utf-8')).toMatch('module.exports = {\n lintOnSave: true\n}')
600+
expect(logs.warn.some(([msg]) => {
601+
return msg.match(/Reserved config transform 'vue'/)
602+
})).toBe(true)
603+
})
604+
513605
test('extract config files', async () => {
514606
const configs = {
515607
vue: {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const transforms = require('./util/configTransforms')
2+
3+
class ConfigTransform {
4+
constructor (options) {
5+
this.fileDescriptor = options.file
6+
}
7+
8+
transform (value, checkExisting, files, context) {
9+
let file
10+
if (checkExisting) {
11+
file = this.findFile(files)
12+
}
13+
if (!file) {
14+
file = this.getDefaultFile()
15+
}
16+
const { type, filename } = file
17+
18+
const transform = transforms[type]
19+
20+
let source
21+
let existing
22+
if (checkExisting) {
23+
source = files[filename]
24+
if (source) {
25+
existing = transform.read({
26+
source,
27+
filename,
28+
context
29+
})
30+
}
31+
}
32+
33+
const content = transform.write({
34+
source,
35+
filename,
36+
context,
37+
value,
38+
existing
39+
})
40+
41+
return {
42+
filename,
43+
content
44+
}
45+
}
46+
47+
findFile (files) {
48+
for (const type of Object.keys(this.fileDescriptor)) {
49+
const descriptors = this.fileDescriptor[type]
50+
for (const filename of descriptors) {
51+
if (files[filename]) {
52+
return { type, filename }
53+
}
54+
}
55+
}
56+
}
57+
58+
getDefaultFile () {
59+
const [type] = Object.keys(this.fileDescriptor)
60+
const [filename] = this.fileDescriptor[type]
61+
return { type, filename }
62+
}
63+
}
64+
65+
module.exports = ConfigTransform

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

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ const debug = require('debug')
33
const GeneratorAPI = require('./GeneratorAPI')
44
const sortObject = require('./util/sortObject')
55
const writeFileTree = require('./util/writeFileTree')
6-
const configTransforms = require('./util/configTransforms')
76
const normalizeFilePaths = require('./util/normalizeFilePaths')
87
const injectImportsAndOptions = require('./util/injectImportsAndOptions')
98
const { toShortPluginId, matchesPluginId } = require('@vue/cli-shared-utils')
9+
const ConfigTransform = require('./ConfigTransform')
1010

1111
const logger = require('@vue/cli-shared-utils/lib/logger')
1212
const logTypes = {
@@ -17,6 +17,46 @@ const logTypes = {
1717
error: logger.error
1818
}
1919

20+
const defaultConfigTransforms = {
21+
babel: new ConfigTransform({
22+
file: {
23+
js: ['babel.config.js']
24+
}
25+
}),
26+
postcss: new ConfigTransform({
27+
file: {
28+
js: ['.postcssrc.js'],
29+
json: ['.postcssrc.json', '.postcssrc'],
30+
yaml: ['.postcssrc.yaml', '.postcssrc.yml']
31+
}
32+
}),
33+
eslintConfig: new ConfigTransform({
34+
file: {
35+
js: ['.eslintrc.js'],
36+
json: ['.eslintrc', '.eslintrc.json'],
37+
yaml: ['.eslintrc.yaml', '.eslintrc.yml']
38+
}
39+
}),
40+
jest: new ConfigTransform({
41+
file: {
42+
js: ['jest.config.js']
43+
}
44+
}),
45+
browserslist: new ConfigTransform({
46+
file: {
47+
lines: ['.browserslistrc']
48+
}
49+
})
50+
}
51+
52+
const reservedConfigTransforms = {
53+
vue: new ConfigTransform({
54+
file: {
55+
js: ['vue.config.js']
56+
}
57+
})
58+
}
59+
2060
module.exports = class Generator {
2161
constructor (context, {
2262
pkg = {},
@@ -32,8 +72,10 @@ module.exports = class Generator {
3272
this.imports = {}
3373
this.rootOptions = {}
3474
this.completeCbs = completeCbs
75+
this.configTransforms = {}
76+
this.defaultConfigTransforms = defaultConfigTransforms
77+
this.reservedConfigTransforms = reservedConfigTransforms
3578
this.invoking = invoking
36-
3779
// for conflict resolution
3880
this.depSources = {}
3981
// virtual file tree
@@ -70,6 +112,11 @@ module.exports = class Generator {
70112
}
71113

72114
extractConfigFiles (extractAll, checkExisting) {
115+
const configTransforms = Object.assign({},
116+
defaultConfigTransforms,
117+
this.configTransforms,
118+
reservedConfigTransforms
119+
)
73120
const extract = key => {
74121
if (
75122
configTransforms[key] &&
@@ -78,8 +125,8 @@ module.exports = class Generator {
78125
!this.originalPkg[key]
79126
) {
80127
const value = this.pkg[key]
81-
const transform = configTransforms[key]
82-
const res = transform(
128+
const configTransform = configTransforms[key]
129+
const res = configTransform.transform(
83130
value,
84131
checkExisting,
85132
this.files,

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const isBinary = require('isbinaryfile')
88
const yaml = require('yaml-front-matter')
99
const mergeDeps = require('./util/mergeDeps')
1010
const stringifyJS = require('./util/stringifyJS')
11-
const { getPluginLink, toShortPluginId } = require('@vue/cli-shared-utils')
11+
const { warn, getPluginLink, toShortPluginId } = require('@vue/cli-shared-utils')
12+
const ConfigTransform = require('./ConfigTransform')
1213

1314
const isString = val => typeof val === 'string'
1415
const isFunction = val => typeof val === 'function'
@@ -81,6 +82,38 @@ class GeneratorAPI {
8182
return this.generator.hasPlugin(id)
8283
}
8384

85+
/**
86+
* Configure how config files are extracted.
87+
*
88+
* @param {string} key - Config key in package.json
89+
* @param {object} options - Options
90+
* @param {object} options.file - File descriptor
91+
* Used to search for existing file.
92+
* Each key is a file type (possible values: ['js', 'json', 'yaml', 'lines']).
93+
* The value is a list of filenames.
94+
* Example:
95+
* {
96+
* js: ['.eslintrc.js'],
97+
* json: ['.eslintrc.json', '.eslintrc']
98+
* }
99+
* By default, the first filename will be used to create the config file.
100+
*/
101+
addConfigTransform (key, options) {
102+
const hasReserved = Object.keys(this.generator.reservedConfigTransforms).includes(key)
103+
if (
104+
hasReserved ||
105+
!options ||
106+
!options.file
107+
) {
108+
if (hasReserved) {
109+
warn(`Reserved config transform '${key}'`)
110+
}
111+
return
112+
}
113+
114+
this.generator.configTransforms[key] = new ConfigTransform(options)
115+
}
116+
84117
/**
85118
* Extend the package.json of the project.
86119
* Nested fields are deep-merged unless `{ merge: false }` is passed.

0 commit comments

Comments
 (0)