-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
utils.js
126 lines (112 loc) · 3.53 KB
/
utils.js
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
import { promises as fs } from "node:fs";
import { join, parse } from "node:path";
import { promisify } from "node:util";
import { exec as execCb } from "node:child_process";
import ncpCb from "ncp";
import ts from "typescript";
import * as cheerio from "cheerio";
import * as acorn from "acorn";
import * as acornWalk from "acorn-walk";
import * as aString from "astring";
const RF_OPTIONS = { recursive: true, force: true };
const exec = promisify(execCb);
const ncp = promisify(ncpCb);
const execCommand = async (command, options) => {
const { stdout, stderr } = await exec(command, options);
if (stderr) throw new Error(stderr);
return stdout.trim();
};
const checkUncommitedChanges = async (options) => {
if (await execCommand(`git status --porcelain`, options)) {
throw new Error("Commit your changes first");
}
};
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
function isTypeScriptProject(cwd) {
const configPath = ts.findConfigFile(cwd, ts.sys.fileExists, "tsconfig.json");
if (!configPath) return false;
return ts.readConfigFile(configPath, ts.sys.readFile).config?.compilerOptions
?.outDir;
}
const pathExists = (path) =>
fs
.access(path)
.then(() => true)
.catch(() => false);
const getFileExtension = (file) => parse(file).ext;
const HMR_HOOK = await fs.readFile(
new URL("./hot.js", import.meta.url),
"utf-8"
);
const htmlHotInject = async (options, req) => {
const html = await fs.readFile(join(options.cwd, req.url), "utf8");
const $ = cheerio.load(html);
// Append the hot script
$("head").append(`<script type="module">${HMR_HOOK}</script>`);
try {
// Set shimMode by parsing json or window.esmsOptions options
const esmsOptionsJSON = $("script[type='esms-options']");
let hasOptions;
if (esmsOptionsJSON.length) {
$("script[type='esms-options']").text(
JSON.stringify(
Object.assign(JSON.parse(esmsOptionsJSON.text()), { shimMode: true })
)
);
hasOptions = true;
} else {
$("script").each((_, element) => {
const ast = acorn.parse($(element).text(), {
ecmaVersion: "latest",
sourceType: ["module", "module-shim"].includes(element.attribs.type)
? "module"
: "script",
});
acornWalk.full(ast, (node) => {
if (node.type === "AssignmentExpression") {
if (
["window", "globalThis"].includes(node.left?.object?.name) &&
node.left?.property?.name === "esmsInitOptions" &&
node.right?.type === "ObjectExpression"
) {
node.right.properties ||= [];
node.right.properties.push({
type: "Property",
method: false,
shorthand: false,
computed: false,
key: { type: "Identifier", name: "shimMode" },
value: { type: "Literal", value: "true", raw: '"true"' },
kind: "init",
});
hasOptions = true;
}
}
});
if (hasOptions) $(element).text(aString.generate(ast));
});
}
if (!hasOptions) {
$("head").append(
`<script type="esms-options">{ "shimMode": true }</script>`
);
}
} catch (error) {
console.error(error);
}
return $.html();
};
export {
RF_OPTIONS,
exec,
ncp,
execCommand,
checkUncommitedChanges,
escapeRegExp,
isTypeScriptProject,
pathExists,
getFileExtension,
htmlHotInject,
};