@pylonsync/functions 0.11.6 → 0.13.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/dist/build-cli.d.ts +1 -0
- package/dist/build-compat.d.ts +81 -0
- package/dist/index.d.ts +1 -1
- package/dist/production-build.d.ts +47 -0
- package/dist/server-bundle.d.ts +31 -0
- package/dist/ssr-client-bundler.d.ts +31 -3
- package/dist/ssr-fonts.d.ts +1 -1
- package/dist/ssr-runtime.d.ts +13 -1
- package/dist/stdout-fence.d.ts +18 -0
- package/dist/types.d.ts +75 -0
- package/package.json +8 -4
- package/src/build-cli.ts +36 -0
- package/src/build-compat.test.ts +173 -0
- package/src/build-compat.ts +430 -0
- package/src/index.ts +3 -0
- package/src/production-build.test.ts +269 -0
- package/src/production-build.ts +590 -0
- package/src/runtime-shard-ticket.test.ts +137 -0
- package/src/runtime.ts +83 -82
- package/src/server-bundle.test.ts +89 -0
- package/src/server-bundle.ts +63 -0
- package/src/ssr-client-bundler.ts +125 -13
- package/src/ssr-fonts.ts +1 -1
- package/src/ssr-og-runtime.ts +17 -5
- package/src/ssr-runtime.ts +112 -47
- package/src/stdout-fence.ts +69 -0
- package/src/types.ts +81 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
// Browser compatibility for the client bundle: syntax lowering, core-js
|
|
2
|
+
// polyfills, and CSS targets. Driven by the manifest's `build` block
|
|
3
|
+
// (`buildManifest({ build: { target, polyfill, css } })`).
|
|
4
|
+
//
|
|
5
|
+
// Bun.build cannot lower syntax to an older target, so lowering runs as a
|
|
6
|
+
// pass over Bun's output:
|
|
7
|
+
//
|
|
8
|
+
// 1. SWC transforms every emitted .js file with `env.targets` (syntax
|
|
9
|
+
// lowering + minify). In `usage` mode SWC also adds an
|
|
10
|
+
// `import "core-js/modules/<feature>.js"` line for each feature the file
|
|
11
|
+
// uses that the targets lack. The pass strips those lines and collects
|
|
12
|
+
// them.
|
|
13
|
+
// 2. The collected set (or, in `entry` mode, every feature the targets
|
|
14
|
+
// lack) is bundled once into `polyfills-<hash>.js`. The SSR head loads it
|
|
15
|
+
// before the route entry.
|
|
16
|
+
// 3. Every .js output gets a new name whose hash covers the transform
|
|
17
|
+
// settings, so a target change never serves a stale immutable file.
|
|
18
|
+
//
|
|
19
|
+
// The tools (@swc/core, core-js, browserslist, lightningcss) are resolved
|
|
20
|
+
// from the app, like @tailwindcss/cli. An app that sets no `build.target`
|
|
21
|
+
// needs none of them.
|
|
22
|
+
|
|
23
|
+
declare const Bun: {
|
|
24
|
+
resolveSync(specifier: string, from: string): string;
|
|
25
|
+
build(options: any): Promise<any>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** The manifest's `build` block, as the SDK writes it. */
|
|
29
|
+
export interface ManifestBuildConfig {
|
|
30
|
+
target?: string | string[];
|
|
31
|
+
polyfill?: false | "usage" | "entry";
|
|
32
|
+
css?: { target?: string | string[] };
|
|
33
|
+
sourcemap?: boolean;
|
|
34
|
+
server?: { bundle?: boolean; external?: string[] };
|
|
35
|
+
include?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Resolved client compatibility settings. `null` means Bun's output ships
|
|
39
|
+
* unchanged (no target set). */
|
|
40
|
+
export interface ClientCompat {
|
|
41
|
+
/** Browserslist queries for JS. Empty when only `css.target` is set. */
|
|
42
|
+
jsTargets: string[];
|
|
43
|
+
/** Browserslist queries for CSS. */
|
|
44
|
+
cssTargets: string[];
|
|
45
|
+
polyfill: false | "usage" | "entry";
|
|
46
|
+
sourcemap: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Browser versions for each ECMAScript edition target. `es2019` becomes the
|
|
51
|
+
* oldest browsers with full ES2019 syntax support, so SWC and Lightning CSS
|
|
52
|
+
* (which work from browser versions) can use it.
|
|
53
|
+
*/
|
|
54
|
+
export const ES_TARGET_BROWSERS: Record<string, string[]> = {
|
|
55
|
+
es2018: ["chrome 64", "edge 79", "firefox 67", "safari 12", "ios 12"],
|
|
56
|
+
es2019: ["chrome 73", "edge 79", "firefox 67", "safari 12.1", "ios 12.2"],
|
|
57
|
+
es2020: ["chrome 80", "edge 80", "firefox 80", "safari 14.1", "ios 14.5"],
|
|
58
|
+
es2021: ["chrome 85", "edge 85", "firefox 80", "safari 14.1", "ios 14.5"],
|
|
59
|
+
es2022: ["chrome 94", "edge 94", "firefox 93", "safari 16.4", "ios 16.4"],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The oldest version of each browser that can run the Pylon client runtime.
|
|
64
|
+
* The runtime ships as ES modules and loads route entries with dynamic
|
|
65
|
+
* `import()`. Syntax lowering cannot make an older browser load a page.
|
|
66
|
+
*/
|
|
67
|
+
export const RUNTIME_BROWSER_FLOOR: Record<string, number> = {
|
|
68
|
+
chrome: 63,
|
|
69
|
+
and_chr: 63,
|
|
70
|
+
edge: 79,
|
|
71
|
+
firefox: 67,
|
|
72
|
+
and_ff: 67,
|
|
73
|
+
safari: 11.1,
|
|
74
|
+
ios_saf: 11.3,
|
|
75
|
+
opera: 50,
|
|
76
|
+
op_mob: 46,
|
|
77
|
+
samsung: 8,
|
|
78
|
+
android: 63,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Normalize a `target` value to browserslist queries. Throws on a value the
|
|
82
|
+
* build cannot honor. */
|
|
83
|
+
export function targetQueries(target: string | string[], field: string): string[] {
|
|
84
|
+
const list = Array.isArray(target) ? target : [target];
|
|
85
|
+
if (list.length === 0) throw new Error(`${field}: must not be empty`);
|
|
86
|
+
const out: string[] = [];
|
|
87
|
+
for (const raw of list) {
|
|
88
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
89
|
+
throw new Error(`${field}: every entry must be a non-empty string`);
|
|
90
|
+
}
|
|
91
|
+
const t = raw.trim();
|
|
92
|
+
const es = t.match(/^es(\d{4})$/i);
|
|
93
|
+
if (es) {
|
|
94
|
+
const key = `es${es[1]}`;
|
|
95
|
+
const browsers = ES_TARGET_BROWSERS[key];
|
|
96
|
+
if (!browsers) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`${field}: "${t}" is not supported. Use one of ${Object.keys(ES_TARGET_BROWSERS).join(", ")}, or browserslist queries. ` +
|
|
99
|
+
`Targets below es2018 cannot run the Pylon client runtime (ES modules and dynamic import()).`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
out.push(...browsers);
|
|
103
|
+
} else {
|
|
104
|
+
out.push(t);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve the client compatibility settings from the manifest's `build`
|
|
112
|
+
* block. Returns null when no JS or CSS target is set. `polyfill` without
|
|
113
|
+
* `target` is an error: it needs the browsers to polyfill for.
|
|
114
|
+
*/
|
|
115
|
+
export function resolveClientCompat(
|
|
116
|
+
build: ManifestBuildConfig | undefined,
|
|
117
|
+
): ClientCompat | null {
|
|
118
|
+
if (!build) return null;
|
|
119
|
+
const polyfill = build.polyfill ?? false;
|
|
120
|
+
if (polyfill !== false && polyfill !== "usage" && polyfill !== "entry") {
|
|
121
|
+
throw new Error(`build.polyfill: must be false, "usage", or "entry"`);
|
|
122
|
+
}
|
|
123
|
+
const sourcemap = build.sourcemap === true;
|
|
124
|
+
if (build.target === undefined) {
|
|
125
|
+
if (polyfill !== false) {
|
|
126
|
+
throw new Error(`build.polyfill needs build.target (the browsers to polyfill for)`);
|
|
127
|
+
}
|
|
128
|
+
if (build.css?.target === undefined) return null;
|
|
129
|
+
const cssTargets = targetQueries(build.css.target, "build.css.target");
|
|
130
|
+
return { jsTargets: [], cssTargets, polyfill: false, sourcemap };
|
|
131
|
+
}
|
|
132
|
+
const jsTargets = targetQueries(build.target, "build.target");
|
|
133
|
+
const cssTargets =
|
|
134
|
+
build.css?.target !== undefined
|
|
135
|
+
? targetQueries(build.css.target, "build.css.target")
|
|
136
|
+
: jsTargets;
|
|
137
|
+
return { jsTargets, cssTargets, polyfill, sourcemap };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The npm package name of a module specifier (`@swc/core/x` → `@swc/core`). */
|
|
141
|
+
function packageName(spec: string): string {
|
|
142
|
+
const parts = spec.split("/");
|
|
143
|
+
return spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Load a build tool from the app's dependencies, with an install hint. */
|
|
147
|
+
async function loadAppTool(cwd: string, spec: string, why: string): Promise<any> {
|
|
148
|
+
let resolved: string;
|
|
149
|
+
try {
|
|
150
|
+
resolved = Bun.resolveSync(spec, cwd);
|
|
151
|
+
} catch {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`${why} needs "${packageName(spec)}" in the app's dependencies. Run: bun add -d ${packageName(spec)}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const mod = await import(resolved);
|
|
157
|
+
// CommonJS packages (browserslist) arrive as `{ default: fn }`.
|
|
158
|
+
return typeof mod.default === "function" ? mod.default : mod;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The browsers (from a browserslist result) that cannot run the client
|
|
162
|
+
* runtime. Browsers with no known floor are included. */
|
|
163
|
+
export function browsersBelowFloor(browsers: string[]): string[] {
|
|
164
|
+
const below: string[] = [];
|
|
165
|
+
for (const b of browsers) {
|
|
166
|
+
const [name, version] = b.split(" ");
|
|
167
|
+
const floor = RUNTIME_BROWSER_FLOOR[name];
|
|
168
|
+
// A range such as "15.2-15.3": compare its lower bound.
|
|
169
|
+
const v = parseFloat((version ?? "").split("-")[0]);
|
|
170
|
+
if (floor === undefined || Number.isNaN(v) || v < floor) below.push(b);
|
|
171
|
+
}
|
|
172
|
+
return below;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const CORE_JS_IMPORT_RE = /import\s*["'](core-js\/modules\/[^"']+)["'];?/g;
|
|
176
|
+
|
|
177
|
+
/** Remove core-js feature imports from `code` and add them to `into`. */
|
|
178
|
+
export function extractCoreJsImports(code: string, into: Set<string>): string {
|
|
179
|
+
return code.replace(CORE_JS_IMPORT_RE, (_m, spec: string) => {
|
|
180
|
+
into.add(spec);
|
|
181
|
+
return "";
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Give a hashed file name (`name-<hash>.js`) a new hash derived from its old
|
|
186
|
+
* one and `salt`. Names without a hash are returned unchanged. */
|
|
187
|
+
export function rehashName(
|
|
188
|
+
base: string,
|
|
189
|
+
salt: string,
|
|
190
|
+
hasher: (s: string) => string,
|
|
191
|
+
): string {
|
|
192
|
+
const m = base.match(/^(.*)-([A-Za-z0-9]+)\.js$/);
|
|
193
|
+
if (!m) return base;
|
|
194
|
+
return `${m[1]}-${hasher(`${m[2]}:${salt}`).slice(0, 10)}.js`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface CompatResult {
|
|
198
|
+
/** Old outdir-relative path → new outdir-relative path, for every renamed
|
|
199
|
+
* output. */
|
|
200
|
+
renamed: Map<string, string>;
|
|
201
|
+
/** Polyfill bundle, outdir-relative. Absent when polyfill is off or the
|
|
202
|
+
* targets need none. */
|
|
203
|
+
polyfills?: string;
|
|
204
|
+
/** Browsers the targets include that cannot run the client runtime. */
|
|
205
|
+
unsupported: string[];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Lower every .js output under `outdir` to the JS targets, collect and bundle
|
|
210
|
+
* polyfills, and rename the outputs. `jsFiles` are outdir-relative,
|
|
211
|
+
* "/"-separated paths of Bun's .js outputs.
|
|
212
|
+
*/
|
|
213
|
+
export async function applyClientCompat(opts: {
|
|
214
|
+
fs: any;
|
|
215
|
+
path: any;
|
|
216
|
+
cwd: string;
|
|
217
|
+
outdir: string;
|
|
218
|
+
jsFiles: string[];
|
|
219
|
+
compat: ClientCompat;
|
|
220
|
+
}): Promise<CompatResult> {
|
|
221
|
+
const { fs, path, cwd, outdir, jsFiles, compat } = opts;
|
|
222
|
+
const renamed = new Map<string, string>();
|
|
223
|
+
if (compat.jsTargets.length === 0) return { renamed, unsupported: [] };
|
|
224
|
+
|
|
225
|
+
const why = "build.target";
|
|
226
|
+
const swc = await loadAppTool(cwd, "@swc/core", why);
|
|
227
|
+
const browserslist = await loadAppTool(cwd, "browserslist", why);
|
|
228
|
+
// The resolved browsers, not the queries: `defaults` or `last 2 versions`
|
|
229
|
+
// resolve to new browsers when browserslist's data updates, and the output
|
|
230
|
+
// (and so the file names) must change with them.
|
|
231
|
+
const browsers: string[] = browserslist(compat.jsTargets);
|
|
232
|
+
const unsupported = browsersBelowFloor(browsers);
|
|
233
|
+
|
|
234
|
+
let coreJsVersion = "";
|
|
235
|
+
if (compat.polyfill !== false) {
|
|
236
|
+
let pkgPath: string;
|
|
237
|
+
try {
|
|
238
|
+
pkgPath = Bun.resolveSync("core-js/package.json", cwd);
|
|
239
|
+
} catch {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`build.polyfill needs "core-js" in the app's dependencies. Run: bun add core-js`,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
const v = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version as string;
|
|
245
|
+
coreJsVersion = v.split(".").slice(0, 2).join(".");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const envFor = (mode: "usage" | "entry" | undefined) => ({
|
|
249
|
+
targets: compat.jsTargets,
|
|
250
|
+
...(mode ? { mode, coreJs: coreJsVersion } : {}),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const features = new Set<string>();
|
|
254
|
+
for (const rel of jsFiles) {
|
|
255
|
+
const abs = path.join(outdir, rel);
|
|
256
|
+
const code = fs.readFileSync(abs, "utf8");
|
|
257
|
+
const mapPath = `${abs}.map`;
|
|
258
|
+
const inputMap =
|
|
259
|
+
compat.sourcemap && fs.existsSync(mapPath)
|
|
260
|
+
? fs.readFileSync(mapPath, "utf8")
|
|
261
|
+
: undefined;
|
|
262
|
+
const out = await swc.transform(code, {
|
|
263
|
+
filename: path.basename(abs),
|
|
264
|
+
isModule: true,
|
|
265
|
+
sourceMaps: compat.sourcemap,
|
|
266
|
+
...(inputMap ? { inputSourceMap: inputMap } : {}),
|
|
267
|
+
jsc: {
|
|
268
|
+
parser: { syntax: "ecmascript" },
|
|
269
|
+
minify: { compress: true, mangle: true },
|
|
270
|
+
},
|
|
271
|
+
env: envFor(compat.polyfill === "usage" ? "usage" : undefined),
|
|
272
|
+
module: { type: "es6" },
|
|
273
|
+
minify: true,
|
|
274
|
+
});
|
|
275
|
+
const lowered = extractCoreJsImports(out.code, features);
|
|
276
|
+
fs.writeFileSync(abs, stripSourceMapComment(lowered), "utf8");
|
|
277
|
+
if (out.map) fs.writeFileSync(mapPath, out.map, "utf8");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (compat.polyfill === "entry") {
|
|
281
|
+
const out = await swc.transform(`import "core-js/stable";`, {
|
|
282
|
+
filename: "polyfills-entry.js",
|
|
283
|
+
isModule: true,
|
|
284
|
+
jsc: { parser: { syntax: "ecmascript" } },
|
|
285
|
+
env: envFor("entry"),
|
|
286
|
+
module: { type: "es6" },
|
|
287
|
+
});
|
|
288
|
+
extractCoreJsImports(out.code, features);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const cryptoMod: any = await import("node:crypto");
|
|
292
|
+
const hasher = (s: string) =>
|
|
293
|
+
cryptoMod.createHash("sha256").update(s).digest("hex");
|
|
294
|
+
const salt = JSON.stringify([
|
|
295
|
+
browsers,
|
|
296
|
+
compat.polyfill,
|
|
297
|
+
compat.sourcemap,
|
|
298
|
+
swc.version ?? "",
|
|
299
|
+
coreJsVersion,
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
// Rename every .js output, then rewrite the references between them. Hashed
|
|
303
|
+
// names are unique tokens, so a plain string replace is exact.
|
|
304
|
+
const baseRenames = new Map<string, string>();
|
|
305
|
+
for (const rel of jsFiles) {
|
|
306
|
+
const base = path.basename(rel);
|
|
307
|
+
const next = rehashName(base, salt, hasher);
|
|
308
|
+
if (next !== base) baseRenames.set(base, next);
|
|
309
|
+
}
|
|
310
|
+
for (const rel of jsFiles) {
|
|
311
|
+
const abs = path.join(outdir, rel);
|
|
312
|
+
let code = fs.readFileSync(abs, "utf8");
|
|
313
|
+
for (const [from, to] of baseRenames) {
|
|
314
|
+
if (code.includes(from)) code = code.split(from).join(to);
|
|
315
|
+
}
|
|
316
|
+
const base = path.basename(rel);
|
|
317
|
+
const nextBase = baseRenames.get(base) ?? base;
|
|
318
|
+
const nextRel = rel.slice(0, rel.length - base.length) + nextBase;
|
|
319
|
+
const nextAbs = path.join(outdir, nextRel);
|
|
320
|
+
if (fs.existsSync(`${abs}.map`)) {
|
|
321
|
+
code += `//# sourceMappingURL=${nextBase}.map\n`;
|
|
322
|
+
if (nextAbs !== abs) fs.renameSync(`${abs}.map`, `${nextAbs}.map`);
|
|
323
|
+
}
|
|
324
|
+
fs.writeFileSync(nextAbs, code, "utf8");
|
|
325
|
+
if (nextAbs !== abs) {
|
|
326
|
+
fs.rmSync(abs);
|
|
327
|
+
renamed.set(rel, nextRel);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let polyfills: string | undefined;
|
|
332
|
+
if (features.size > 0) {
|
|
333
|
+
polyfills = await buildPolyfills({
|
|
334
|
+
fs,
|
|
335
|
+
path,
|
|
336
|
+
cwd,
|
|
337
|
+
outdir,
|
|
338
|
+
features: [...features].sort(),
|
|
339
|
+
swc,
|
|
340
|
+
env: envFor(undefined),
|
|
341
|
+
hasher,
|
|
342
|
+
salt,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { renamed, polyfills, unsupported };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function stripSourceMapComment(code: string): string {
|
|
350
|
+
return code.replace(/\n?\/\/# sourceMappingURL=\S+\s*$/, "") + "\n";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Bundle the core-js feature modules into one lowered file in `outdir`.
|
|
354
|
+
* Returns its outdir-relative path. */
|
|
355
|
+
async function buildPolyfills(opts: {
|
|
356
|
+
fs: any;
|
|
357
|
+
path: any;
|
|
358
|
+
cwd: string;
|
|
359
|
+
outdir: string;
|
|
360
|
+
features: string[];
|
|
361
|
+
swc: any;
|
|
362
|
+
env: Record<string, unknown>;
|
|
363
|
+
hasher: (s: string) => string;
|
|
364
|
+
salt: string;
|
|
365
|
+
}): Promise<string> {
|
|
366
|
+
const { fs, path, cwd, outdir, features, swc, env, hasher, salt } = opts;
|
|
367
|
+
const stageDir = path.join(cwd, ".pylon");
|
|
368
|
+
fs.mkdirSync(stageDir, { recursive: true });
|
|
369
|
+
const entry = path.join(stageDir, "polyfills-entry.js");
|
|
370
|
+
fs.writeFileSync(
|
|
371
|
+
entry,
|
|
372
|
+
features.map((f) => `import ${JSON.stringify(f)};`).join("\n") + "\n",
|
|
373
|
+
"utf8",
|
|
374
|
+
);
|
|
375
|
+
const tmpOut = path.join(stageDir, "polyfills-build");
|
|
376
|
+
fs.rmSync(tmpOut, { recursive: true, force: true });
|
|
377
|
+
const result = await Bun.build({
|
|
378
|
+
entrypoints: [entry],
|
|
379
|
+
outdir: tmpOut,
|
|
380
|
+
root: cwd,
|
|
381
|
+
target: "browser",
|
|
382
|
+
format: "esm",
|
|
383
|
+
minify: true,
|
|
384
|
+
});
|
|
385
|
+
if (!result.success) {
|
|
386
|
+
const msgs = (result.logs ?? []).map((l: any) => l.message).join("\n");
|
|
387
|
+
throw new Error(`polyfill bundle failed:\n${msgs}`);
|
|
388
|
+
}
|
|
389
|
+
const built = result.outputs.find((o: any) => o.path.endsWith(".js"));
|
|
390
|
+
const code = fs.readFileSync(built.path, "utf8");
|
|
391
|
+
const out = await swc.transform(code, {
|
|
392
|
+
filename: "polyfills.js",
|
|
393
|
+
isModule: true,
|
|
394
|
+
jsc: {
|
|
395
|
+
parser: { syntax: "ecmascript" },
|
|
396
|
+
minify: { compress: true, mangle: true },
|
|
397
|
+
},
|
|
398
|
+
env,
|
|
399
|
+
module: { type: "es6" },
|
|
400
|
+
minify: true,
|
|
401
|
+
});
|
|
402
|
+
const name = `polyfills-${hasher(`${out.code}:${salt}`).slice(0, 10)}.js`;
|
|
403
|
+
fs.writeFileSync(path.join(outdir, name), out.code, "utf8");
|
|
404
|
+
fs.rmSync(tmpOut, { recursive: true, force: true });
|
|
405
|
+
fs.rmSync(entry, { force: true });
|
|
406
|
+
return name;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Apply Lightning CSS to compiled CSS for the CSS targets: vendor prefixes,
|
|
411
|
+
* nesting and color-syntax lowering, minification.
|
|
412
|
+
*/
|
|
413
|
+
export async function transformCss(
|
|
414
|
+
cwd: string,
|
|
415
|
+
css: string,
|
|
416
|
+
filename: string,
|
|
417
|
+
cssTargets: string[],
|
|
418
|
+
): Promise<string> {
|
|
419
|
+
const why = "build.css.target (or build.target)";
|
|
420
|
+
const lightningcss = await loadAppTool(cwd, "lightningcss", why);
|
|
421
|
+
const browserslist = await loadAppTool(cwd, "browserslist", why);
|
|
422
|
+
const targets = lightningcss.browserslistToTargets(browserslist(cssTargets));
|
|
423
|
+
const out = lightningcss.transform({
|
|
424
|
+
filename,
|
|
425
|
+
code: Buffer.from(css),
|
|
426
|
+
minify: true,
|
|
427
|
+
targets,
|
|
428
|
+
});
|
|
429
|
+
return out.code.toString();
|
|
430
|
+
}
|