@pharos-ds/build 0.1.9
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/LICENSE +24 -0
- package/README.md +258 -0
- package/dist/vite.d.ts +91 -0
- package/dist/vite.mjs +287 -0
- package/package.json +63 -0
- package/src/babel.js +106 -0
- package/src/babel.test.mjs +73 -0
- package/src/config.js +92 -0
- package/src/index.js +251 -0
- package/src/next.js +72 -0
- package/src/vite.test.ts +48 -0
- package/src/vite.ts +429 -0
package/src/vite.test.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file vite.test.ts
|
|
5
|
+
* @description Verifies CSS layer-order injection in the XDS Vite plugin.
|
|
6
|
+
* The library layer name is configurable (default `pharos-base`); the
|
|
7
|
+
* theme layer name is fixed at `pharos-theme`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {describe, it, expect} from 'vitest';
|
|
11
|
+
import {pharosStylex} from './vite';
|
|
12
|
+
|
|
13
|
+
/** Pull the injected `@layer ...;` order statement out of the plugin set. */
|
|
14
|
+
function getLayerOrder(plugins: ReturnType<typeof pharosStylex>): string {
|
|
15
|
+
const layerPlugin = plugins.find(p => p.name === 'pharos-css-layer-order');
|
|
16
|
+
expect(
|
|
17
|
+
layerPlugin,
|
|
18
|
+
'pharos-css-layer-order plugin should exist',
|
|
19
|
+
).toBeTruthy();
|
|
20
|
+
const transform = (layerPlugin as any).transformIndexHtml;
|
|
21
|
+
const tags =
|
|
22
|
+
typeof transform === 'function' ? transform() : transform.handler();
|
|
23
|
+
const styleTag = tags.find((t: any) => t.tag === 'style');
|
|
24
|
+
expect(styleTag, 'a <style> tag should be injected').toBeTruthy();
|
|
25
|
+
return styleTag.children as string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('pharosStylex layer order (modern API)', () => {
|
|
29
|
+
it('uses the pharos-* layer names (theme layer is pharos-theme)', () => {
|
|
30
|
+
const order = getLayerOrder(pharosStylex());
|
|
31
|
+
expect(order).toBe('@layer reset, pharos-base, pharos-theme, product;');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('honors configured library and product layer names', () => {
|
|
35
|
+
const order = getLayerOrder(
|
|
36
|
+
pharosStylex({layers: {library: 'custom-base', product: 'app'}}),
|
|
37
|
+
);
|
|
38
|
+
// The theme layer stays pharos-theme regardless of other layer config.
|
|
39
|
+
expect(order).toBe('@layer reset, custom-base, pharos-theme, app;');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('pharosStylex layer order (legacy API)', () => {
|
|
44
|
+
it('uses the pharos-* layer names (theme layer is pharos-theme)', () => {
|
|
45
|
+
const order = getLayerOrder(pharosStylex({stylexOptions: {}}));
|
|
46
|
+
expect(order).toBe('@layer reset, pharos-base, pharos-theme, product;');
|
|
47
|
+
});
|
|
48
|
+
});
|
package/src/vite.ts
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import type {Plugin, UserConfig} from 'vite';
|
|
4
|
+
import stylexBabelPlugin from '@stylexjs/babel-plugin';
|
|
5
|
+
import stylex from '@stylexjs/unplugin';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
const LIBRARY_PATTERN = 'node_modules/@pharos-ds/';
|
|
12
|
+
const STYLEX_CSS_PATH = '/virtual:stylex.css';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Browser targets for lightningcss (opt-in).
|
|
16
|
+
* Only needed if your StyleX version lowers light-dark() without them.
|
|
17
|
+
* Exported for consumers who want to opt in explicitly.
|
|
18
|
+
*/
|
|
19
|
+
export const LIGHTNINGCSS_TARGETS = {
|
|
20
|
+
chrome: 123 << 16,
|
|
21
|
+
firefox: 120 << 16,
|
|
22
|
+
safari: (17 << 16) | (5 << 8),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Legacy options shape — kept for backward compatibility.
|
|
27
|
+
* Prefer the zero-config form: pharosStylex()
|
|
28
|
+
*/
|
|
29
|
+
export interface PharosVitePluginLegacyOptions {
|
|
30
|
+
stylexOptions: Parameters<typeof stylex.vite>[0];
|
|
31
|
+
libraryPattern?: string;
|
|
32
|
+
/** StyleX atomic class-name prefix for Pharos library styles. @default 'pharos' */
|
|
33
|
+
stylexPrefix?: string;
|
|
34
|
+
layers?: {
|
|
35
|
+
library?: string;
|
|
36
|
+
product?: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PharosVitePluginOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Whether to enable dev mode for StyleX.
|
|
43
|
+
* @default process.env.NODE_ENV !== 'production'
|
|
44
|
+
*/
|
|
45
|
+
dev?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Root directory for module resolution.
|
|
49
|
+
* @default process.cwd()
|
|
50
|
+
*/
|
|
51
|
+
rootDir?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Pattern to identify Pharos library files vs product files.
|
|
55
|
+
* @default 'node_modules/@pharos-ds/'
|
|
56
|
+
*/
|
|
57
|
+
libraryPattern?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* CSS layer names for the split output.
|
|
61
|
+
*/
|
|
62
|
+
layers?: {
|
|
63
|
+
/** Layer name for Pharos library styles @default 'pharos-base' */
|
|
64
|
+
library?: string;
|
|
65
|
+
/** Layer name for product styles @default 'product' */
|
|
66
|
+
product?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* LightningCSS browser targets. Only needed if your StyleX version
|
|
71
|
+
* lowers light-dark() without them. Most recent versions preserve
|
|
72
|
+
* light-dark() by default.
|
|
73
|
+
* @default undefined (no targets set)
|
|
74
|
+
*/
|
|
75
|
+
lightningcssTargets?: Record<string, number>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* StyleX atomic class-name prefix for Pharos *library* styles. The product
|
|
79
|
+
* build uses a distinct prefix so library and product atoms never collide
|
|
80
|
+
* across layers.
|
|
81
|
+
*
|
|
82
|
+
* Configurable if a consumer needs a custom library atom prefix.
|
|
83
|
+
* Defaults to `pharos`.
|
|
84
|
+
*
|
|
85
|
+
* @default 'pharos'
|
|
86
|
+
*/
|
|
87
|
+
stylexPrefix?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extra StyleX options to merge.
|
|
91
|
+
*/
|
|
92
|
+
stylexOverrides?: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Pharos Vite plugin for source builds.
|
|
97
|
+
*
|
|
98
|
+
* Provides sensible defaults for StyleX compilation with Pharos.
|
|
99
|
+
* Just spread into your plugins array:
|
|
100
|
+
*
|
|
101
|
+
* plugins: [...pharosStylex(), react()]
|
|
102
|
+
*
|
|
103
|
+
* Handles:
|
|
104
|
+
* - StyleX compilation with correct settings
|
|
105
|
+
* - CSS layer ordering (reset < pharos-base < pharos-theme < product)
|
|
106
|
+
* - resolve.alias for @pharos-ds/core source
|
|
107
|
+
* - optimizeDeps.exclude to prevent Vite pre-bundling Pharos
|
|
108
|
+
*
|
|
109
|
+
* @param options — optional overrides
|
|
110
|
+
*/
|
|
111
|
+
export function pharosStylex(
|
|
112
|
+
options: PharosVitePluginOptions | PharosVitePluginLegacyOptions = {},
|
|
113
|
+
): Plugin[] {
|
|
114
|
+
// Detect legacy API: pharosStylex({stylexOptions: {...}})
|
|
115
|
+
if ('stylexOptions' in options && options.stylexOptions) {
|
|
116
|
+
return pharosStylexLegacy(options as PharosVitePluginLegacyOptions);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const opts = options as PharosVitePluginOptions;
|
|
120
|
+
const {
|
|
121
|
+
dev = process.env.NODE_ENV !== 'production',
|
|
122
|
+
rootDir = process.cwd(),
|
|
123
|
+
libraryPattern = LIBRARY_PATTERN,
|
|
124
|
+
layers = {},
|
|
125
|
+
lightningcssTargets,
|
|
126
|
+
stylexPrefix = 'pharos',
|
|
127
|
+
stylexOverrides = {},
|
|
128
|
+
} = opts;
|
|
129
|
+
|
|
130
|
+
const libraryLayer = layers.library ?? 'pharos-base';
|
|
131
|
+
const productLayer = layers.product ?? 'product';
|
|
132
|
+
|
|
133
|
+
// Build StyleX options with sensible defaults
|
|
134
|
+
const stylexOptions: Record<string, unknown> = {
|
|
135
|
+
dev,
|
|
136
|
+
runtimeInjection: false,
|
|
137
|
+
treeshakeCompensation: true,
|
|
138
|
+
unstable_moduleResolution: {
|
|
139
|
+
type: 'commonJS',
|
|
140
|
+
rootDir,
|
|
141
|
+
},
|
|
142
|
+
...(lightningcssTargets && {
|
|
143
|
+
lightningcssOptions: {targets: lightningcssTargets},
|
|
144
|
+
}),
|
|
145
|
+
...stylexOverrides,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Inject our babel wrapper as a user plugin — it runs before the
|
|
149
|
+
// unplugin's hardcoded StyleX instance and handles prefix routing.
|
|
150
|
+
const pharosBabelPlugin = path.resolve(__dirname, 'babel.js');
|
|
151
|
+
|
|
152
|
+
const basePlugin = stylex.vite({
|
|
153
|
+
...(stylexOptions as any),
|
|
154
|
+
useCSSLayers: true,
|
|
155
|
+
babelConfig: {
|
|
156
|
+
plugins: [
|
|
157
|
+
[
|
|
158
|
+
pharosBabelPlugin,
|
|
159
|
+
{
|
|
160
|
+
...stylexOptions,
|
|
161
|
+
libraryPrefix: stylexPrefix,
|
|
162
|
+
babelConfig: undefined,
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Layer order declaration plugin
|
|
170
|
+
const layerOrderPlugin: Plugin = {
|
|
171
|
+
name: 'pharos-css-layer-order',
|
|
172
|
+
transformIndexHtml() {
|
|
173
|
+
return [
|
|
174
|
+
{
|
|
175
|
+
tag: 'style',
|
|
176
|
+
children: `@layer reset, ${libraryLayer}, pharos-theme, ${productLayer};`,
|
|
177
|
+
injectTo: 'head-prepend',
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Config plugin — injects resolve.alias and optimizeDeps
|
|
184
|
+
const configPlugin: Plugin = {
|
|
185
|
+
name: 'pharos-config',
|
|
186
|
+
config(): UserConfig {
|
|
187
|
+
// Discover all @pharos-ds/* packages to exclude from pre-bundling.
|
|
188
|
+
// Pharos ships as source that must be compiled by StyleX — pre-bundling
|
|
189
|
+
// strips stylex.create/defineVars calls and causes runtime errors.
|
|
190
|
+
let xdsPackages: string[] = ['@pharos-ds/core'];
|
|
191
|
+
try {
|
|
192
|
+
const fs = require('node:fs');
|
|
193
|
+
const xdsDir = path.resolve(rootDir, 'node_modules/@pharos-ds');
|
|
194
|
+
if (fs.existsSync(xdsDir)) {
|
|
195
|
+
xdsPackages = fs
|
|
196
|
+
.readdirSync(xdsDir)
|
|
197
|
+
.filter((name: string) => !name.startsWith('.'))
|
|
198
|
+
.map((name: string) => `@pharos-ds/${name}`);
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Fallback to just @pharos-ds/core if discovery fails
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
resolve: {
|
|
206
|
+
alias: {
|
|
207
|
+
'@pharos-ds/core/theme/tokens.stylex': path.resolve(
|
|
208
|
+
rootDir,
|
|
209
|
+
'node_modules/@pharos-ds/core/src/theme/tokens.stylex.ts',
|
|
210
|
+
),
|
|
211
|
+
'@pharos-ds/core': path.resolve(
|
|
212
|
+
rootDir,
|
|
213
|
+
'node_modules/@pharos-ds/core/src',
|
|
214
|
+
),
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
optimizeDeps: {
|
|
218
|
+
exclude: xdsPackages,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Split-layer interceptor plugin (dev server only)
|
|
225
|
+
const splitLayerPlugin: Plugin = {
|
|
226
|
+
name: 'pharos-split-layers',
|
|
227
|
+
configureServer(server) {
|
|
228
|
+
let stylexPlugin: any = null;
|
|
229
|
+
|
|
230
|
+
return () => {
|
|
231
|
+
for (const p of server.config.plugins.flat()) {
|
|
232
|
+
if ((p as any)?.__stylexGetSharedStore) {
|
|
233
|
+
stylexPlugin = p;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
server.middlewares.stack.unshift({
|
|
239
|
+
route: '',
|
|
240
|
+
handle: (req: any, res: any, next: any) => {
|
|
241
|
+
if (!req.url?.startsWith(STYLEX_CSS_PATH)) {
|
|
242
|
+
return next();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!stylexPlugin) {
|
|
246
|
+
res.statusCode = 200;
|
|
247
|
+
res.setHeader('Content-Type', 'text/css');
|
|
248
|
+
res.end('');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const shared = stylexPlugin.__stylexGetSharedStore?.();
|
|
253
|
+
const rulesById = shared?.rulesById;
|
|
254
|
+
|
|
255
|
+
if (!rulesById || rulesById.size === 0) {
|
|
256
|
+
res.statusCode = 200;
|
|
257
|
+
res.setHeader('Content-Type', 'text/css');
|
|
258
|
+
res.end('');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const libraryRules: any[] = [];
|
|
263
|
+
const productRules: any[] = [];
|
|
264
|
+
|
|
265
|
+
for (const [filePath, rules] of rulesById.entries()) {
|
|
266
|
+
if (filePath.includes(libraryPattern)) {
|
|
267
|
+
libraryRules.push(...rules);
|
|
268
|
+
} else {
|
|
269
|
+
productRules.push(...rules);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const libraryCss = libraryRules.length
|
|
274
|
+
? stylexBabelPlugin.processStylexRules(libraryRules, {
|
|
275
|
+
useLayers: true,
|
|
276
|
+
})
|
|
277
|
+
: '';
|
|
278
|
+
const productCss = productRules.length
|
|
279
|
+
? stylexBabelPlugin.processStylexRules(productRules, {
|
|
280
|
+
useLayers: true,
|
|
281
|
+
})
|
|
282
|
+
: '';
|
|
283
|
+
|
|
284
|
+
const parts: string[] = [];
|
|
285
|
+
if (libraryCss)
|
|
286
|
+
parts.push(`@layer ${libraryLayer} {\n${libraryCss}\n}`);
|
|
287
|
+
if (productCss)
|
|
288
|
+
parts.push(`@layer ${productLayer} {\n${productCss}\n}`);
|
|
289
|
+
|
|
290
|
+
res.statusCode = 200;
|
|
291
|
+
res.setHeader('Content-Type', 'text/css');
|
|
292
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
293
|
+
res.end(parts.join('\n\n'));
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
return [configPlugin, layerOrderPlugin, basePlugin, splitLayerPlugin];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Legacy implementation — handles the old pharosStylex({stylexOptions: {...}}) API.
|
|
305
|
+
* Used by Storybook and other existing configs.
|
|
306
|
+
*/
|
|
307
|
+
function pharosStylexLegacy(options: PharosVitePluginLegacyOptions): Plugin[] {
|
|
308
|
+
const {
|
|
309
|
+
stylexOptions,
|
|
310
|
+
libraryPattern = LIBRARY_PATTERN,
|
|
311
|
+
stylexPrefix = 'pharos',
|
|
312
|
+
layers = {},
|
|
313
|
+
} = options;
|
|
314
|
+
|
|
315
|
+
const libraryLayer = layers.library ?? 'pharos-base';
|
|
316
|
+
const productLayer = layers.product ?? 'product';
|
|
317
|
+
|
|
318
|
+
const pharosBabelPlugin = path.resolve(__dirname, 'babel.js');
|
|
319
|
+
const existingPlugins = (stylexOptions as any).babelConfig?.plugins ?? [];
|
|
320
|
+
|
|
321
|
+
const basePlugin = stylex.vite({
|
|
322
|
+
...(stylexOptions as any),
|
|
323
|
+
useCSSLayers: true,
|
|
324
|
+
babelConfig: {
|
|
325
|
+
...(stylexOptions as any).babelConfig,
|
|
326
|
+
plugins: [
|
|
327
|
+
[
|
|
328
|
+
pharosBabelPlugin,
|
|
329
|
+
{
|
|
330
|
+
...(stylexOptions as any),
|
|
331
|
+
libraryPrefix: stylexPrefix,
|
|
332
|
+
babelConfig: undefined,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
...existingPlugins,
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const layerOrderPlugin: Plugin = {
|
|
341
|
+
name: 'pharos-css-layer-order',
|
|
342
|
+
transformIndexHtml() {
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
tag: 'style',
|
|
346
|
+
children: `@layer reset, ${libraryLayer}, pharos-theme, ${productLayer};`,
|
|
347
|
+
injectTo: 'head-prepend',
|
|
348
|
+
},
|
|
349
|
+
];
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const splitLayerPlugin: Plugin = {
|
|
354
|
+
name: 'pharos-split-layers',
|
|
355
|
+
configureServer(server) {
|
|
356
|
+
let stylexPlugin: any = null;
|
|
357
|
+
|
|
358
|
+
return () => {
|
|
359
|
+
for (const p of server.config.plugins.flat()) {
|
|
360
|
+
if ((p as any)?.__stylexGetSharedStore) {
|
|
361
|
+
stylexPlugin = p;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
server.middlewares.stack.unshift({
|
|
367
|
+
route: '',
|
|
368
|
+
handle: (req: any, res: any, next: any) => {
|
|
369
|
+
if (!req.url?.startsWith(STYLEX_CSS_PATH)) {
|
|
370
|
+
return next();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!stylexPlugin) {
|
|
374
|
+
res.statusCode = 200;
|
|
375
|
+
res.setHeader('Content-Type', 'text/css');
|
|
376
|
+
res.end('');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const shared = stylexPlugin.__stylexGetSharedStore?.();
|
|
381
|
+
const rulesById = shared?.rulesById;
|
|
382
|
+
|
|
383
|
+
if (!rulesById || rulesById.size === 0) {
|
|
384
|
+
res.statusCode = 200;
|
|
385
|
+
res.setHeader('Content-Type', 'text/css');
|
|
386
|
+
res.end('');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const libraryRules: any[] = [];
|
|
391
|
+
const productRules: any[] = [];
|
|
392
|
+
|
|
393
|
+
for (const [filePath, rules] of rulesById.entries()) {
|
|
394
|
+
if (filePath.includes(libraryPattern)) {
|
|
395
|
+
libraryRules.push(...rules);
|
|
396
|
+
} else {
|
|
397
|
+
productRules.push(...rules);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const libraryCss = libraryRules.length
|
|
402
|
+
? stylexBabelPlugin.processStylexRules(libraryRules, {
|
|
403
|
+
useLayers: true,
|
|
404
|
+
})
|
|
405
|
+
: '';
|
|
406
|
+
const productCss = productRules.length
|
|
407
|
+
? stylexBabelPlugin.processStylexRules(productRules, {
|
|
408
|
+
useLayers: true,
|
|
409
|
+
})
|
|
410
|
+
: '';
|
|
411
|
+
|
|
412
|
+
const parts: string[] = [];
|
|
413
|
+
if (libraryCss)
|
|
414
|
+
parts.push(`@layer ${libraryLayer} {\n${libraryCss}\n}`);
|
|
415
|
+
if (productCss)
|
|
416
|
+
parts.push(`@layer ${productLayer} {\n${productCss}\n}`);
|
|
417
|
+
|
|
418
|
+
res.statusCode = 200;
|
|
419
|
+
res.setHeader('Content-Type', 'text/css');
|
|
420
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
421
|
+
res.end(parts.join('\n\n'));
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
return [layerOrderPlugin, basePlugin, splitLayerPlugin];
|
|
429
|
+
}
|