vite-plugin-qingkuai 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +7 -0
- package/package.json +43 -0
- package/rollup.config.js +41 -0
- package/src/create-sourcemap-files.ts +24 -0
- package/src/index.ts +256 -0
- package/src/scope.ts +46 -0
- package/src/sourcemap.ts +38 -0
- package/src/types.ts +19 -0
- package/src/util.ts +21 -0
- package/tsconfig.json +9 -0
package/.prettierrc
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-qingkuai",
|
|
3
|
+
"author": "mlgq",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"vite",
|
|
9
|
+
"qingkuai",
|
|
10
|
+
"vite plugin",
|
|
11
|
+
"vite-plugin"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "rollup -c -w",
|
|
21
|
+
"build": "rimraf dist && rollup -c"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/fs-extra": "^11.0.4",
|
|
25
|
+
"@types/node": "^22.13.14",
|
|
26
|
+
"fs-extra": "^11.3.0",
|
|
27
|
+
"qingkuai": "^1.0.24",
|
|
28
|
+
"rimraf": "^6.0.1",
|
|
29
|
+
"rollup": "^4.38.0",
|
|
30
|
+
"rollup-plugin-esbuild": "^6.2.1",
|
|
31
|
+
"vite": "^6.2.4"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"qingkuai": "^1.0.24",
|
|
35
|
+
"vite": "^6.2.4"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
|
39
|
+
"postcss": "^8.5.3",
|
|
40
|
+
"postcss-selector-parser": "^7.1.0",
|
|
41
|
+
"source-map-js": "^1.2.1"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as rollup from "rollup"
|
|
2
|
+
import esbuild from "rollup-plugin-esbuild"
|
|
3
|
+
|
|
4
|
+
export default rollup.defineConfig(() => {
|
|
5
|
+
const inputOptions = {
|
|
6
|
+
external: [
|
|
7
|
+
"vite",
|
|
8
|
+
"postcss",
|
|
9
|
+
"node:path",
|
|
10
|
+
"merge-source-map",
|
|
11
|
+
"qingkuai/compiler",
|
|
12
|
+
"@ampproject/remapping",
|
|
13
|
+
"postcss-selector-parser",
|
|
14
|
+
"@jridgewell/sourcemap-codec"
|
|
15
|
+
],
|
|
16
|
+
input: {
|
|
17
|
+
index: "./src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
output: {
|
|
20
|
+
dir: "dist",
|
|
21
|
+
format: "es",
|
|
22
|
+
sourcemap: true
|
|
23
|
+
},
|
|
24
|
+
plugins: [
|
|
25
|
+
esbuild({
|
|
26
|
+
target: "esNext"
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
inputOptions,
|
|
33
|
+
Object.assign({}, inputOptions, {
|
|
34
|
+
output: {
|
|
35
|
+
dir: "dist",
|
|
36
|
+
format: "cjs",
|
|
37
|
+
entryFileNames: "[name].cjs"
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SourceMap } from "./types"
|
|
2
|
+
|
|
3
|
+
import fs from "fs-extra"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
|
|
6
|
+
export function generateSourceMapFiles(generate: string, map: SourceMap) {
|
|
7
|
+
const sourcemapPath = path.resolve(__dirname, "../sourcemap-test")
|
|
8
|
+
const gpath = path.resolve(sourcemapPath, "./g.css")
|
|
9
|
+
const mpath = path.resolve(sourcemapPath, "./s.css.map")
|
|
10
|
+
fs.removeSync(gpath)
|
|
11
|
+
fs.removeSync(mpath)
|
|
12
|
+
fs.writeFileSync(gpath, generate, "utf-8")
|
|
13
|
+
fs.writeFileSync(
|
|
14
|
+
mpath,
|
|
15
|
+
JSON.stringify({
|
|
16
|
+
version: 3,
|
|
17
|
+
names: map.names,
|
|
18
|
+
sources: map.sources,
|
|
19
|
+
mappings: map.mappings,
|
|
20
|
+
sourcesContent: map.sourcesContent
|
|
21
|
+
}),
|
|
22
|
+
"utf-8"
|
|
23
|
+
)
|
|
24
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import type { CompileResult } from "qingkuai/compiler"
|
|
2
|
+
import type { QingkuaiConfiguration, SourceMap } from "./types"
|
|
3
|
+
import type { Plugin, ResolvedConfig, ViteDevServer } from "vite"
|
|
4
|
+
|
|
5
|
+
import { findFilesByName } from "./util"
|
|
6
|
+
import { randomBytes } from "node:crypto"
|
|
7
|
+
import { offsetSourceMap } from "./sourcemap"
|
|
8
|
+
import { compile, util } from "qingkuai/compiler"
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
10
|
+
import { encode } from "@jridgewell/sourcemap-codec"
|
|
11
|
+
import { basename, dirname, extname } from "node:path"
|
|
12
|
+
import { attachScopeForStyleSelectors } from "./scope"
|
|
13
|
+
import { transformWithEsbuild, preprocessCSS } from "vite"
|
|
14
|
+
|
|
15
|
+
export default function qingkuaiPlugin(): Plugin {
|
|
16
|
+
let isDev: boolean
|
|
17
|
+
let sourcemap: boolean
|
|
18
|
+
let cssSourcemap: boolean
|
|
19
|
+
let viteConfig: ResolvedConfig
|
|
20
|
+
|
|
21
|
+
const parseFailedConfigFiles: string[] = []
|
|
22
|
+
const compileResultCache = new Map<string, CompileResult>()
|
|
23
|
+
const qingkuaiConfigurations = new Map<string, QingkuaiConfiguration>()
|
|
24
|
+
const styleIdRE = /^virtual:\[\d+\].*?\.qk.(?:css|s[ac]ss|less|stylus|postcss)\?\d{13}$/
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
name: "qingkuai-compiler",
|
|
28
|
+
|
|
29
|
+
config(_, env) {
|
|
30
|
+
isDev = env.command === "serve"
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
configureServer(server) {
|
|
34
|
+
createQingkuaiConfigurationWatcher(server)
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
configResolved(config) {
|
|
38
|
+
viteConfig = config
|
|
39
|
+
if (isDev) {
|
|
40
|
+
sourcemap = true
|
|
41
|
+
cssSourcemap = !!config.css.devSourcemap
|
|
42
|
+
} else {
|
|
43
|
+
cssSourcemap = true
|
|
44
|
+
sourcemap = config.build.sourcemap !== false
|
|
45
|
+
}
|
|
46
|
+
loadAllQingkuaiConfigurations(config.root)
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
resolveId(id, importer) {
|
|
50
|
+
if (styleIdRE.test(id)) {
|
|
51
|
+
return id
|
|
52
|
+
}
|
|
53
|
+
if (importer?.endsWith(".qk") && !extname(id)) {
|
|
54
|
+
const qingkuaiConfig = getQingkuaiConfiguration(id)
|
|
55
|
+
return id + (qingkuaiConfig.resolveImportExtension ? ".qk" : "")
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async load(id) {
|
|
60
|
+
if (styleIdRE.test(id)) {
|
|
61
|
+
const { fileId, index } = parseStyleId(id)
|
|
62
|
+
if (index === -1) {
|
|
63
|
+
return ""
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let virtualFileName: string
|
|
67
|
+
const compileRes = compileResultCache.get(fileId)!
|
|
68
|
+
const style = compileRes.inputDescriptor.style[index]
|
|
69
|
+
|
|
70
|
+
// create a relative and not existing file name
|
|
71
|
+
while (true) {
|
|
72
|
+
const hash = randomBytes(6).toString("hex")
|
|
73
|
+
virtualFileName = `${fileId}.${hash}.${style.lang}`
|
|
74
|
+
if (!existsSync(virtualFileName)) {
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const preprocessRes = await preprocessCSS(style.code, virtualFileName, viteConfig)
|
|
80
|
+
if (!cssSourcemap) {
|
|
81
|
+
return preprocessRes.code
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const assertedPreprocessMap = preprocessRes.map as SourceMap
|
|
85
|
+
const attachScopeResult = await attachScopeForStyleSelectors(
|
|
86
|
+
preprocessRes.code,
|
|
87
|
+
compileRes.hashId,
|
|
88
|
+
assertedPreprocessMap,
|
|
89
|
+
virtualFileName
|
|
90
|
+
)
|
|
91
|
+
const offsetMappings = encode(
|
|
92
|
+
offsetSourceMap(
|
|
93
|
+
attachScopeResult.mappings,
|
|
94
|
+
preprocessRes.deps?.size || 0,
|
|
95
|
+
style.loc.start.line - 1,
|
|
96
|
+
style.loc.start.column - 1
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
return {
|
|
100
|
+
code: attachScopeResult.code,
|
|
101
|
+
map: {
|
|
102
|
+
version: 3,
|
|
103
|
+
mappings: offsetMappings,
|
|
104
|
+
names: assertedPreprocessMap.names,
|
|
105
|
+
sources: [...assertedPreprocessMap.sources.slice(0, -1), fileId]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
async transform(src, id) {
|
|
112
|
+
if (!id.endsWith(".qk")) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const qingkuaiConfig = getQingkuaiConfiguration(id)
|
|
117
|
+
const compileRes = compile(src, {
|
|
118
|
+
sourcemap,
|
|
119
|
+
debug: isDev,
|
|
120
|
+
hashId: compileResultCache.get(id)?.hashId || undefined,
|
|
121
|
+
reserveTemplateComment: getReserveHtmlComments(qingkuaiConfig),
|
|
122
|
+
componentName: util.kebab2Camel(basename(id, extname(id)), true)
|
|
123
|
+
})
|
|
124
|
+
compileRes.messages.forEach(({ type, value: warning }) => {
|
|
125
|
+
if (type === "warning") {
|
|
126
|
+
this.warn(warning.message)
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
compileResultCache.set(id, compileRes)
|
|
130
|
+
|
|
131
|
+
const compiledCodeArr = [compileRes.code]
|
|
132
|
+
compileRes.inputDescriptor.style.forEach((_, index) => {
|
|
133
|
+
compiledCodeArr.push(`import "virtual:[${index}]${id}.css?${Date.now()}"`)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const baseMap: any = {
|
|
137
|
+
version: 3,
|
|
138
|
+
sources: [id],
|
|
139
|
+
sourcesContent: [src]
|
|
140
|
+
}
|
|
141
|
+
const compiledCode = compiledCodeArr.join("\n")
|
|
142
|
+
if (!compileRes.inputDescriptor.script.isTS) {
|
|
143
|
+
if (!sourcemap) {
|
|
144
|
+
return compiledCode
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
code: compiledCode,
|
|
148
|
+
map: Object.assign(baseMap, {
|
|
149
|
+
mappings: compileRes.mappings
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const esBuildCompileRes = await transformWithEsbuild(
|
|
155
|
+
compiledCode,
|
|
156
|
+
id,
|
|
157
|
+
{
|
|
158
|
+
sourcemap,
|
|
159
|
+
loader: "ts",
|
|
160
|
+
target: "esnext"
|
|
161
|
+
},
|
|
162
|
+
sourcemap
|
|
163
|
+
? {
|
|
164
|
+
version: 3,
|
|
165
|
+
sources: [id],
|
|
166
|
+
sourcesContent: [src],
|
|
167
|
+
mappings: compileRes.mappings
|
|
168
|
+
}
|
|
169
|
+
: undefined
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if (!sourcemap) {
|
|
173
|
+
return esBuildCompileRes.code
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
code: esBuildCompileRes.code,
|
|
177
|
+
map: Object.assign(baseMap, {
|
|
178
|
+
mappings: esBuildCompileRes.map.mappings
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getQingkuaiConfiguration(id: string) {
|
|
185
|
+
let config: QingkuaiConfiguration = {
|
|
186
|
+
insertTipComments: true,
|
|
187
|
+
exposeDestruction: isDev,
|
|
188
|
+
exposeDependencies: isDev,
|
|
189
|
+
resolveImportExtension: true,
|
|
190
|
+
reserveHtmlComments: "development"
|
|
191
|
+
}
|
|
192
|
+
while (true) {
|
|
193
|
+
const dir = dirname(id)
|
|
194
|
+
if (dir === id) {
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const got = qingkuaiConfigurations.get((id = dir))
|
|
199
|
+
if (got) {
|
|
200
|
+
config = got
|
|
201
|
+
break
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return config
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getReserveHtmlComments(config: QingkuaiConfiguration) {
|
|
208
|
+
switch (config.reserveHtmlComments) {
|
|
209
|
+
case "all":
|
|
210
|
+
return true
|
|
211
|
+
case "never":
|
|
212
|
+
return false
|
|
213
|
+
case "production":
|
|
214
|
+
return !isDev
|
|
215
|
+
default:
|
|
216
|
+
return isDev
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function createQingkuaiConfigurationWatcher(server: ViteDevServer) {
|
|
221
|
+
const watcher = server.watcher.add([".qingkuairc", "!**/node_modules/**"])
|
|
222
|
+
watcher.on("unlink", path => qingkuaiConfigurations.delete(dirname(path)))
|
|
223
|
+
watcher.on("change", recordQingkuaiConfiguration)
|
|
224
|
+
watcher.on("add", recordQingkuaiConfiguration)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function loadAllQingkuaiConfigurations(workspaceDir: string) {
|
|
228
|
+
findFilesByName(workspaceDir, ".qingkuairc", new Set(["node_modules"])).forEach(fileName => {
|
|
229
|
+
recordQingkuaiConfiguration(fileName)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function recordQingkuaiConfiguration(fileName: string) {
|
|
234
|
+
try {
|
|
235
|
+
qingkuaiConfigurations.set(dirname(fileName), JSON.parse(readFileSync(fileName, "utf-8")))
|
|
236
|
+
} catch {
|
|
237
|
+
parseFailedConfigFiles.push(fileName)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function parseStyleId(id: string) {
|
|
243
|
+
const m1 = /^virtual:\[(\d+)\]/.exec(id)!
|
|
244
|
+
const m2 = /\.[a-z]+\?\d{13}$/.exec(id)!
|
|
245
|
+
if (!m1 || !m2) {
|
|
246
|
+
return {
|
|
247
|
+
index: -1,
|
|
248
|
+
fileId: ""
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
index: parseInt(m1[1]),
|
|
254
|
+
fileId: id.slice(m1.index + m1[0].length, m2.index)
|
|
255
|
+
}
|
|
256
|
+
}
|
package/src/scope.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SourceMap } from "./types"
|
|
2
|
+
|
|
3
|
+
import postcss from "postcss"
|
|
4
|
+
import { getSourceFile } from "./sourcemap"
|
|
5
|
+
import selectorParser from "postcss-selector-parser"
|
|
6
|
+
|
|
7
|
+
export async function attachScopeForStyleSelectors(code: string, hash: string, map: SourceMap, sourceFile: string) {
|
|
8
|
+
const processor = postcss([
|
|
9
|
+
{
|
|
10
|
+
postcssPlugin: "postcss-attach-scope-qingkuai",
|
|
11
|
+
Rule(rule) {
|
|
12
|
+
const { line, column } = rule.source!.start!
|
|
13
|
+
if (getSourceFile(map, line, column) !== sourceFile) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
rule.selector = selectorParser(selectors => {
|
|
18
|
+
selectors.each(selector => {
|
|
19
|
+
const index = selector.nodes.findLastIndex(({ type }) => {
|
|
20
|
+
return type === "class" || type === "tag" || type === "id" || type === "universal"
|
|
21
|
+
})
|
|
22
|
+
if (index !== -1) {
|
|
23
|
+
const lastNode = selector.nodes[index]!
|
|
24
|
+
lastNode.parent?.insertAfter(
|
|
25
|
+
lastNode,
|
|
26
|
+
selectorParser.attribute({
|
|
27
|
+
attribute: `qk-${hash}`,
|
|
28
|
+
value: undefined,
|
|
29
|
+
raws: {}
|
|
30
|
+
})
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}).processSync(rule.selector)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
])
|
|
38
|
+
|
|
39
|
+
const ret = await processor.process(code, {
|
|
40
|
+
map: {
|
|
41
|
+
prev: map,
|
|
42
|
+
annotation: false
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
return { code: ret.css, mappings: ret.map.toJSON().mappings }
|
|
46
|
+
}
|
package/src/sourcemap.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SourceMap } from "./types"
|
|
2
|
+
import type { SourceMapMappings } from "@jridgewell/sourcemap-codec"
|
|
3
|
+
|
|
4
|
+
import { SourceMapConsumer } from "source-map-js"
|
|
5
|
+
import { decode } from "@jridgewell/sourcemap-codec"
|
|
6
|
+
|
|
7
|
+
export function offsetSourceMap(
|
|
8
|
+
mappings: string,
|
|
9
|
+
sourceIndex: number,
|
|
10
|
+
preLine: number,
|
|
11
|
+
preColumn: number
|
|
12
|
+
): SourceMapMappings {
|
|
13
|
+
return decode(mappings).map(line => {
|
|
14
|
+
return line.map(segment => {
|
|
15
|
+
if (segment.length === 1 || segment[1] !== sourceIndex) {
|
|
16
|
+
return segment
|
|
17
|
+
}
|
|
18
|
+
if (segment[2] === preLine) {
|
|
19
|
+
segment[3] += preColumn
|
|
20
|
+
} else {
|
|
21
|
+
segment[2] += preLine
|
|
22
|
+
}
|
|
23
|
+
return segment
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getSourceFile(sourcemap: SourceMap, line: number, column: number) {
|
|
29
|
+
const consumer = new SourceMapConsumer({
|
|
30
|
+
...sourcemap,
|
|
31
|
+
version: sourcemap.version.toString()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return consumer.originalPositionFor({
|
|
35
|
+
line: line,
|
|
36
|
+
column: column
|
|
37
|
+
}).source
|
|
38
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SourceMap {
|
|
2
|
+
version: number
|
|
3
|
+
file?: string
|
|
4
|
+
names: string[]
|
|
5
|
+
mappings: string
|
|
6
|
+
sources: string[]
|
|
7
|
+
sourceRoot?: string
|
|
8
|
+
sourcesContent?: string[]
|
|
9
|
+
x_google_ignoreList?: number[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type QingkuaiConfiguration = Partial<{
|
|
13
|
+
exposeDestruction: boolean
|
|
14
|
+
exposeDependencies: boolean
|
|
15
|
+
insertTipComments: boolean
|
|
16
|
+
resolveImportExtension: boolean
|
|
17
|
+
convenientDerivedDeclaration: boolean
|
|
18
|
+
reserveHtmlComments: "all" | "never" | "development" | "production"
|
|
19
|
+
}>
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { join } from "node:path"
|
|
2
|
+
import { readdirSync } from "node:fs"
|
|
3
|
+
|
|
4
|
+
export function findFilesByName(dir: string, targetFileName: string, ignoreList: Set<string>) {
|
|
5
|
+
const result: string[] = []
|
|
6
|
+
const entries = readdirSync(dir, { withFileTypes: true })
|
|
7
|
+
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
if (ignoreList.has(entry.name)) {
|
|
10
|
+
continue
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const fullPath = join(dir, entry.name)
|
|
14
|
+
if (entry.name === targetFileName) {
|
|
15
|
+
result.push(fullPath)
|
|
16
|
+
} else if (entry.isDirectory()) {
|
|
17
|
+
result.push(...findFilesByName(fullPath, targetFileName, ignoreList))
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result
|
|
21
|
+
}
|