forked from stencil-community/unplugin-stencil
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
175 lines (154 loc) · 5.81 KB
/
Copy pathindex.ts
File metadata and controls
175 lines (154 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import type * as CoreCompiler from '@stencil/core/compiler'
import type { OutputTargetDistCustomElements } from '@stencil/core/internal'
import type { UnpluginFactory } from 'unplugin'
import type { Options } from './types.js'
import path from 'node:path'
import process from 'node:process'
import { createCompiler } from '@stencil/core/compiler'
import nodeApi from '@stencil/core/sys/node'
import { findStaticImports, parseStaticImport } from 'mlly'
import { createUnplugin } from 'unplugin'
import { BuildQueue } from './build-queue'
import { STENCIL_IMPORT } from './constants.js'
import { getRootDir, getStencilConfigFile, parseTagConfig, transformCompiledCode } from './utils.js'
const DCE_OUTPUT_TARGET_NAME = 'dist-custom-elements'
export const unpluginFactory: UnpluginFactory<Options | undefined> = (options = {}) => {
const nodeLogger = nodeApi.createNodeLogger()
let distCustomElementsOptions: OutputTargetDistCustomElements | undefined
let compiler: CoreCompiler.Compiler | undefined
let buildQueue: BuildQueue | undefined
const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true'
return {
name: 'unplugin-stencil',
enforce: 'pre',
/**
* This hook is called when the build starts. It is a good place to initialize
*/
async buildStart() {
const configPath = await getStencilConfigFile(options)
const nodeSys = nodeApi.createNodeSys({ process, logger: nodeLogger })
nodeApi.setupNodeProcess({ process, logger: nodeLogger })
const coreCompiler = await nodeSys.dynamicImport!(nodeSys.getCompilerExecutingPath()) as { loadConfig: typeof CoreCompiler.loadConfig }
const validated = await coreCompiler.loadConfig({
config: {
rootDir: getRootDir(options),
tsCompilerOptions: {
skipLibCheck: true,
},
flags: {
task: 'build' as const,
args: [],
knownArgs: [],
unknownArgs: [],
},
},
configPath,
logger: nodeLogger,
sys: nodeSys,
})
distCustomElementsOptions = validated.config.outputTargets.find(o => o.type === DCE_OUTPUT_TARGET_NAME) as OutputTargetDistCustomElements
if (!distCustomElementsOptions)
throw new Error(`Could not find "${DCE_OUTPUT_TARGET_NAME}" output target`)
compiler = await createCompiler(validated.config)
buildQueue = new BuildQueue(compiler)
},
async buildEnd() {
// Clean up compiler resources when build ends
await compiler?.destroy()
compiler = undefined
buildQueue = undefined
// In test mode, force exit after a short delay to allow cleanup
// This works around Stencil compiler not fully releasing file handles
if (isTest) {
setTimeout(() => {
process.exit(0)
}, 100)
}
},
/**
* `transformInclude` is called for every file that is being transformed.
* If it returns `true`, the file will be transformed.
* @param id path of the file
* @returns whether the file should be transformed
*/
transformInclude(id) {
return id.endsWith('.tsx')
},
/**
* try to resolve any dynamic imported file through the compiler output directory
* @param id the id to resolve
* @returns the resolved id or null if not found
*/
resolveId(id) {
if (id.startsWith('.') && compiler && distCustomElementsOptions?.dir) {
const compiledPath = path.resolve(distCustomElementsOptions.dir, id)
try {
const exists = compiler.sys.accessSync(compiledPath)
if (exists) {
return compiledPath
}
}
catch {
return null
}
}
return null
},
/**
* This hook is called when a file is being transformed.
* @param code the source code of the file
* @param id path of the file
* @returns the transformed code
*/
async transform(code, id) {
const staticImports = findStaticImports(code)
const imports = staticImports
.filter(imp => imp.specifier === STENCIL_IMPORT)
.map(imp => parseStaticImport(imp))
const isStencilComponent = imports.some(imp => 'Component' in (imp.namedImports || {}))
/**
* don't compile the file if:
*/
if (
/**
* something with the setup failed and some of the primitives we need
* to compile the file are missing
*/
!compiler || !buildQueue
/**
* the output directory is not set
*/
|| !distCustomElementsOptions || !distCustomElementsOptions.dir
/**
* the file is not a Stencil component and not a CSS file
*/
|| (!isStencilComponent && !id.endsWith('.css'))
) {
return
}
const componentTag = parseTagConfig(code)
const compilerFilePath = path.resolve(distCustomElementsOptions.dir, `${componentTag}.js`)
const raw = await buildQueue.getLatestBuild(id, compilerFilePath)
const exists = await compiler.sys.access(compilerFilePath)
if (!exists)
throw new Error('Could not find the output file')
const transformedCode = await transformCompiledCode(
raw,
compilerFilePath,
)
const sourcemapFilePath = path.resolve(distCustomElementsOptions.dir, `${componentTag}.js.map`)
const rawSourcemap = await buildQueue.getLatestBuild(id, sourcemapFilePath)
const sourcemapExists = await compiler.sys.access(sourcemapFilePath)
const sourcemap = sourcemapExists
? { sourceRoot: getRootDir(options), ...JSON.parse(rawSourcemap) }
: undefined
return {
code: transformedCode,
inputFilePath: id,
map: sourcemap,
}
},
}
}
export const unplugin = /* #__PURE__ */ createUnplugin(unpluginFactory)
export default unplugin