Skip to content

Commit 95915b4

Browse files
committed
Created a utility that runs esbuild and SWC.
This supports a compat profile that works as far back as safari 8, maybe earlier. This also fixes some oversights in the load function that were hidden by the old reify implementation.
1 parent 6408975 commit 95915b4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+1707
-989
lines changed

buildLib/commandLineHelper.mjs

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import process from 'node:process';
2+
3+
export default function createCommandLineArgumentsHandler(validArguments) {
4+
const validTypes = ['json', 'string', 'number', 'boolean'];
5+
const argumentInfo = {};
6+
const shortArgsMap = {};
7+
const defaultTemplate = {};
8+
{
9+
const inputKeys = Object.keys(validArguments);
10+
for(let i = 0; i < inputKeys.length; i++) {
11+
const key = inputKeys[i];
12+
const firstCode = key[0].charCodeAt(0);
13+
if((firstCode < 65 || firstCode > 90) && (firstCode < 97 || firstCode > 122))
14+
continue;
15+
const argument = validArguments[key];
16+
if(argument.type && validTypes.includes(argument.type)){
17+
argumentInfo[key] = {};
18+
argumentInfo[key].type = argument.type;
19+
argumentInfo[key].hasShortKey = argument.shortKey??(argument.type === 'boolean');
20+
argumentInfo[key].takesParameter = (argument.type !== 'boolean'? true : argument.takesParameter??false);
21+
argumentInfo[key].acceptsPropertySyntax = argument.propertySyntax;
22+
if(argument.default){
23+
defaultTemplate[key] = argument.default;
24+
}
25+
}
26+
}
27+
validArguments = null;
28+
}
29+
const argumentKeys = Object.keys(argumentInfo);
30+
for(let i = 0; i < argumentKeys.length; i++) {
31+
const key = argumentKeys[i];
32+
if(!argumentInfo[key].hasShortKey)
33+
continue;
34+
let shortKey = null;
35+
let shortKeyCandidate = key[0].toLowerCase();
36+
for(let j = 0; j < 26; j++){
37+
if(!shortArgsMap[shortKeyCandidate]){
38+
shortArgsMap[shortKeyCandidate] = key;
39+
shortKey = shortKeyCandidate;
40+
break;
41+
}else if(!shortArgsMap[shortKeyCandidate.toUpperCase()]){
42+
shortArgsMap[shortKeyCandidate.toUpperCase()] = key;
43+
shortKey = shortKeyCandidate.toUpperCase();
44+
break;
45+
}
46+
shortKeyCandidate = String.fromCharCode(((shortKeyCandidate.charCodeAt(0)-95)%26)+96);
47+
console.log(shortKeyCandidate);
48+
}
49+
if(!shortKey)
50+
throw new Error(`Could not assign short key for argument: ${key}`);
51+
}
52+
53+
function checkForSplitValue(value, args, index){
54+
if(value[0] == "'"){
55+
return value[value.length-1] !== "'";
56+
}else if(value[0] == '"'){
57+
return value[value.length-1] !== '"';
58+
}
59+
return false;
60+
}
61+
62+
function parseBooleanArgument(key, args, index, options, value){
63+
if(value !== null && !argumentInfo[key].takesParameter){
64+
throw new Error(`Invalid option: ${key}`);
65+
}else if(value === null && !argumentInfo[key].takesParameter){
66+
options[key] = true;
67+
return 0;
68+
}else if(argumentInfo[key].takesParameter){
69+
let increment = 0;
70+
if(value === null && args.length > index+1){
71+
value = args[index+1];
72+
increment = 1;
73+
}else if(value === null){
74+
throw new Error(`Invalid option: ${key}`);
75+
}else if(checkForSplitValue(value)){
76+
do{
77+
if(args.length <= index+increment)
78+
throw new Error(`Unclosed option value: ${key}`);
79+
value += ' ' + args[index+1];
80+
increment++;
81+
}while(checkForSplitValue(value));
82+
value = value.slice(1,-1);
83+
}
84+
options[key] = value;
85+
return increment;
86+
}else{
87+
throw new Error(`Invalid option: ${key}`);
88+
}
89+
}
90+
91+
function parseNumberArgument(key, args, index, options, value){
92+
let increment = 0;
93+
if(value === null && args.length > index+1){
94+
value = args[index+1];
95+
increment = 1;
96+
}
97+
if(value === null)
98+
throw new Error(`Invalid option: ${key}`);
99+
if(checkForSplitValue(value))
100+
throw new Error(`Unclosed option value: ${key}`);
101+
if(value.startsWith("0x")){
102+
options[key] = parseInt(value.slice(2), 16);
103+
}else if(value.startsWith("0o")){
104+
options[key] = parseInt(value.slice(2), 8);
105+
}else if(value.startsWith("0b")){
106+
options[key] = parseInt(value.slice(2), 2);
107+
}else{
108+
if(value.startsWith("0d")){
109+
options[key]=parseInt(value.slice(2),10);
110+
} else options[key] = parseFloat(value);
111+
}
112+
return increment;
113+
}
114+
115+
function parseStringArgument(key, args, index, options, value){
116+
let increment = 0;
117+
if(value === null && args.length > index+1){
118+
value = args[index+1];
119+
increment = 1;
120+
}
121+
if(value === null)
122+
throw new Error(`Invalid option: ${key}`);
123+
if(checkForSplitValue(value)){
124+
do{
125+
if(args.length <= index+increment)
126+
throw new Error(`Unclosed option value: ${key}`);
127+
value += ' ' + args[index+1];
128+
increment++;
129+
}while(checkForSplitValue(value));
130+
value = value.slice(1,-1);
131+
}
132+
options[key] = value;
133+
return increment;
134+
}
135+
136+
function parseJsonArgument(key, args, index, options, value){
137+
let increment = 0;
138+
if(value === null && args.length > index+1){
139+
value = args[index+1];
140+
increment = 1;
141+
}
142+
if(value === null)
143+
throw new Error(`Invalid option: ${key}`);
144+
if(checkForSplitValue(value)){
145+
do{
146+
if(args.length <= index+increment)
147+
throw new Error(`Unclosed option value: ${key}`);
148+
value += ' ' + args[index+1];
149+
increment++;
150+
}while(checkForSplitValue(value));
151+
value = value.slice(1,-1);
152+
}
153+
options[key] = JSON.parse(value.replaceAll("'", "\""));
154+
return increment;
155+
}
156+
157+
158+
return function() {
159+
const args = process.argv.slice(2);
160+
const parsedArgs = {args: [], options: Object.assign({}, defaultTemplate)};
161+
162+
for (let i = 0; i < args.length; i++) {
163+
const arg = args[i];
164+
if (arg.startsWith('--')) {
165+
const longArg = arg.slice(2);
166+
let key = longArg;
167+
let value = null;
168+
if (!argumentInfo[key.replace(/-/g, '_')]) {
169+
let splitproperty = longArg.split("=");
170+
if (splitproperty.length > 1) {
171+
key = splitproperty[0];
172+
value = splitproperty[1];
173+
}else{
174+
throw new Error(`Invalid option: ${key}`);
175+
}
176+
if(!argumentInfo[key])
177+
throw new Error(`Invalid option: ${key}`);
178+
if(!argumentInfo[key].acceptsPropertySyntax)
179+
throw new Error(`Invalid property syntax for option: ${key}`);
180+
}
181+
if(key.indexOf('_') !== -1)
182+
throw new Error(`Invalid option: ${key}`);
183+
key = key.replace(/-/g, '_');
184+
switch(argumentInfo[key].type) {
185+
case 'boolean':
186+
i += parseBooleanArgument(key, args, i, parsedArgs.options, value);
187+
break;
188+
case 'number':
189+
i += parseNumberArgument(key, args, i, parsedArgs.options, value);
190+
break;
191+
case 'string':
192+
i += parseStringArgument(key, args, i, parsedArgs.options, value);
193+
break;
194+
case 'json':
195+
i += parseJsonArgument(key, args, i, parsedArgs.options, value);
196+
break;
197+
}
198+
} else if (arg.startsWith('-')) {
199+
const shortArg = arg.slice(1);
200+
for (let j = 0; j < shortArg.length; j++) {
201+
const key = shortArgsMap[shortArg[j]];
202+
if (!key) {
203+
throw new Error(`Invalid option: ${shortArg[j]}`);
204+
}
205+
if(argumentInfo[key].type === 'boolean'){
206+
i += parseBooleanArgument(key, args, i, parsedArgs.options, null);
207+
}else if(j > 0 || shortArg.length > 1){
208+
throw new Error(`Invalid option: ${shortArg}`);
209+
}else{
210+
switch(argumentInfo[key].type) {
211+
case 'number':
212+
i += parseNumberArgument(key, args, i, parsedArgs.options, null);
213+
break;
214+
case 'string':
215+
i += parseStringArgument(key, args, i, parsedArgs.options, null);
216+
break;
217+
case 'json':
218+
i += parseJsonArgument(key, args, i, parsedArgs.options, null);
219+
break;
220+
}
221+
}
222+
}
223+
}else{
224+
parsedArgs.args.push(arg);
225+
}
226+
}
227+
228+
return parsedArgs;
229+
}
230+
};

buildLib/esbuild-plugin-swc.mjs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
This file's license:
3+
4+
MIT License
5+
6+
Copyright (c) 2021 sanyuan et al.
7+
8+
Permission is hereby granted, free of charge, to any person obtaining a copy
9+
of this software and associated documentation files (the "Software"), to deal
10+
in the Software without restriction, including without limitation the rights
11+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
copies of the Software, and to permit persons to whom the Software is
13+
furnished to do so, subject to the following conditions:
14+
15+
The above copyright notice and this permission notice shall be included in all
16+
copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
SOFTWARE.
25+
*/
26+
27+
import { transform, transformSync } from '@swc/core';
28+
import path from 'node:path';
29+
import process from 'node:process';
30+
import fs from 'node:fs/promises';
31+
32+
function assignDeep(target, source) {
33+
for (const key in source) {
34+
if (source[key] instanceof Object && !(source[key] instanceof Function)) {
35+
if (!target[key]) {
36+
target[key] = {};
37+
}
38+
assignDeep(target[key], source[key]);
39+
} else {
40+
target[key] = source[key];
41+
}
42+
}
43+
return target;
44+
}
45+
46+
export default function swcPlugin(options, isAsync) {
47+
options = options ?? {};
48+
isAsync = isAsync ?? true;
49+
return {
50+
name: 'esbuild:swc',
51+
setup(builder) {
52+
builder.onResolve({ filter: /\.(m?[tj]s)$/ }, (args) => {
53+
const fullPath = path.resolve(args.resolveDir, args.path);
54+
return {
55+
path: fullPath,
56+
};
57+
});
58+
builder.onLoad({ filter: /\.(m?[tj]s)$/ }, async (args) => {
59+
const code = await fs.readFile(args.path, 'utf-8');
60+
const isTS = args.path.endsWith('.ts');
61+
const initialOptions = {
62+
jsc: {
63+
parser: {
64+
syntax: isTS ? 'typescript' : 'ecmascript',
65+
}
66+
},
67+
filename: args.path,
68+
sourceMaps: true,
69+
sourceFileName: path.relative(options.root,args.path)
70+
};
71+
const finalOptions = assignDeep(assignDeep({}, initialOptions), options);
72+
let result;
73+
if (isAsync) {
74+
result = await transform(code, finalOptions);
75+
}else{
76+
result = transformSync(code, finalOptions);
77+
}
78+
return {
79+
contents: result.code+(finalOptions.sourceMaps?`\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(result.map).toString('base64')}`:''),
80+
loader: 'js'
81+
};
82+
});
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)