-
Notifications
You must be signed in to change notification settings - Fork 805
/
Copy pathext-transforms-plugin.ts
213 lines (189 loc) · 7.84 KB
/
ext-transforms-plugin.ts
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import { hasError, isOutputTargetDistCollection, join, mergeIntoWith, normalizeFsPath, relative } from '@utils';
import type { Plugin } from 'rollup';
import type * as d from '../../declarations';
import { runPluginTransformsEsmImports } from '../plugin/plugin';
import { getScopeId } from '../style/scope-css';
import { parseImportPath } from '../transformers/stencil-import-path';
/**
* This keeps a map of all the component styles we've seen already so we can create
* a correct state of all styles when we're doing a rebuild. This map helps by
* storing the state of all styles as follows, e.g.:
*
* ```
* {
* 'cmp-a-$': {
* '/path/to/project/cmp-a.scss': 'button{color:red}',
* '/path/to/project/cmp-a.md.scss': 'button{color:blue}'
* }
* ```
*
* Whenever one of the files change, we can propagate a correct concatenated
* version of all styles to the browser by setting `buildCtx.stylesUpdated`.
*/
type ComponentStyleMap = Map<string, string>;
const allCmpStyles = new Map<string, ComponentStyleMap>();
/**
* A Rollup plugin which bundles up some transformation of CSS imports as well
* as writing some files to disk for the `DIST_COLLECTION` output target.
*
* @param config a user-supplied configuration
* @param compilerCtx the current compiler context
* @param buildCtx the current build context
* @returns a Rollup plugin which carries out the necessary work
*/
export const extTransformsPlugin = (
config: d.ValidatedConfig,
compilerCtx: d.CompilerCtx,
buildCtx: d.BuildCtx,
): Plugin => {
return {
name: 'extTransformsPlugin',
/**
* A custom function targeting the `transform` build hook in Rollup. See here for details:
* https://rollupjs.org/guide/en/#transform
*
* Here we are ignoring the first argument (which contains the module's source code) and
* only looking at the `id` argument. We use that `id` to get information about the module
* in question from disk ourselves so that we can then do some transformations on it.
*
* @param _ an unused parameter (normally the code for a given module)
* @param id the id of a module
* @returns metadata for Rollup or null if no transformation should be done
*/
async transform(_, id) {
if (/\0/.test(id)) {
return null;
}
/**
* Make sure compiler context has a registered worker. The interface suggests that it
* potentially can be undefined, therefore check for it here.
*/
if (!compilerCtx.worker) {
return null;
}
// The `id` here was possibly previously updated using
// `serializeImportPath` to annotate the filepath with various metadata
// serialized to query-params. If that was done for this particular `id`
// then the `data` prop will not be null.
const { data } = parseImportPath(id);
if (data != null) {
let cmpStyles: ComponentStyleMap | undefined = undefined;
let cmp: d.ComponentCompilerMeta | undefined = undefined;
const filePath = normalizeFsPath(id);
const code = await compilerCtx.fs.readFile(filePath);
if (typeof code !== 'string') {
return null;
}
/**
* add file to watch list if it is outside of the `srcDir` config path
*/
if (config.watch && (id.startsWith('/') || id.startsWith('.')) && !id.startsWith(config.srcDir)) {
compilerCtx.addWatchFile(id.split('?')[0]);
}
const pluginTransforms = await runPluginTransformsEsmImports(config, compilerCtx, buildCtx, code, filePath);
if (data.tag) {
cmp = buildCtx.components.find((c) => c.tagName === data.tag);
const moduleFile = cmp && compilerCtx.moduleMap.get(cmp.sourceFilePath);
if (moduleFile) {
const collectionDirs = config.outputTargets.filter(isOutputTargetDistCollection);
const relPath = relative(config.srcDir, pluginTransforms.id);
// If we found a `moduleFile` in the module map above then we
// should write the transformed CSS file (found in the return value
// of `runPluginTransformsEsmImports`, above) to disk.
await Promise.all(
collectionDirs.map(async (outputTarget) => {
const collectionPath = join(outputTarget.collectionDir, relPath);
await compilerCtx.fs.writeFile(collectionPath, pluginTransforms.code);
}),
);
}
/**
* initiate map for component styles
*/
const scopeId = getScopeId(data.tag, data.mode);
if (!allCmpStyles.has(scopeId)) {
allCmpStyles.set(scopeId, new Map());
}
cmpStyles = allCmpStyles.get(scopeId);
}
const cssTransformResults = await compilerCtx.worker.transformCssToEsm({
file: pluginTransforms.id,
input: pluginTransforms.code,
tag: data.tag,
encapsulation: data.encapsulation,
mode: data.mode,
sourceMap: config.sourceMap,
minify: config.minifyCss,
autoprefixer: config.autoprefixCss,
docs: config.buildDocs,
});
/**
* persist component styles for transformed stylesheet
*/
if (cmpStyles) {
cmpStyles.set(filePath, cssTransformResults.styleText);
}
// Set style docs
if (cmp) {
cmp.styleDocs ||= [];
mergeIntoWith(cmp.styleDocs, cssTransformResults.styleDocs, (docs) => `${docs.name},${docs.mode}`);
}
// Track dependencies
for (const dep of pluginTransforms.dependencies) {
this.addWatchFile(dep);
compilerCtx.addWatchFile(dep);
}
buildCtx.diagnostics.push(...pluginTransforms.diagnostics);
buildCtx.diagnostics.push(...cssTransformResults.diagnostics);
const didError = hasError(cssTransformResults.diagnostics) || hasError(pluginTransforms.diagnostics);
if (didError) {
this.error('Plugin CSS transform error');
}
const hasUpdatedStyle = buildCtx.stylesUpdated.some((s) => {
return s.styleTag === data.tag && s.styleMode === data.mode && s.styleText === cssTransformResults.styleText;
});
/**
* if the style has updated, compose all styles for the component
*/
if (!hasUpdatedStyle && data.tag && data.mode) {
const externalStyles = cmp?.styles?.[0]?.externalStyles;
/**
* if component has external styles, use a list to keep the order to which
* styles are applied.
*/
const styleText = cmpStyles
? externalStyles
? /**
* attempt to find the original `filePath` key through `originalComponentPath`
* and `absolutePath` as path can differ based on how Stencil is installed
* e.g. through `npm link` or `npm install`
*/
externalStyles
.map((es) => cmpStyles.get(es.originalComponentPath) || cmpStyles.get(es.absolutePath))
.join('\n')
: /**
* if `externalStyles` is not defined, then created the style text in the
* order of which the styles were compiled.
*/
[...cmpStyles.values()].join('\n')
: /**
* if `cmpStyles` is not defined, then use the style text from the transform
* as it is not connected to a component.
*/
cssTransformResults.styleText;
buildCtx.stylesUpdated.push({
styleTag: data.tag,
styleMode: data.mode,
styleText,
});
}
return {
code: cssTransformResults.output,
map: cssTransformResults.map,
moduleSideEffects: false,
};
}
return null;
},
};
};