Skip to content

Commit f591344

Browse files
authored
feat: script esm (#38)
* feat: script esm * doc: add no-experience-esm flag * chore: add changeset file
1 parent 62ab7d6 commit f591344

24 files changed

+490
-250
lines changed

.changeset/chatty-monkeys-learn.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'farmup': patch
3+
---
4+
5+
upgrade @farmfe/core version and add exec script without output

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ in your code, if not find `package` or `source`, set external
121121
### sourcemap
122122

123123
- `default`: `undefined`
124-
- option: `boolean` | `'inline'` | `'all'` | `'all-inline'`
124+
- option: `boolean` | `'inline'` | `'all'` | `'all-inline'`
125125

126126
generate sourcemap
127+
128+
### no-experience-esm
129+
130+
- option: `boolean`
131+
132+
disable exec esm without output to filesystem

packages/core/farm.config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ export default defineConfig({
66
input: {
77
index: 'src/index.ts',
88
plugin: 'src/plugin-entry.ts',
9+
import_register: './src/node/esm/register.ts',
10+
import_hooks: './src/node/esm/hooks.ts',
911
},
1012
output: {
1113
targetEnv: 'node',
1214
format: 'esm',
1315
},
16+
script: {
17+
nativeTopLevelAwait: true,
18+
plugins: [],
19+
},
1420
lazyCompilation: false,
1521
persistentCache: false,
1622
external: ['^@farmfe/core$'],

packages/core/package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
},
1919
"bin": "./bin/farmup.js",
2020
"devDependencies": {
21-
"@farmfe/cli": "^1.0.3",
21+
"@farmfe/cli": "^1.0.4",
2222
"@types/fs-extra": "^11.0.4",
2323
"@types/lodash-es": "^4.17.12",
2424
"@types/node": "^20.12.7",
25+
"@types/tmp": "^0.2.6",
2526
"cac": "^6.7.14",
2627
"execa": "^8.0.1",
2728
"fs-extra": "^11.2.0",
@@ -63,6 +64,7 @@
6364
"email": "[email protected]"
6465
},
6566
"dependencies": {
66-
"@farmfe/core": "^1.3.12"
67+
"@farmfe/core": "^1.3.16",
68+
"tmp": "^0.2.3"
6769
}
6870
}

packages/core/src/config/normalize/index.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ function normalizedExecuted(commonOption: CommonOptions, options: ResolvedCommon
7474
args: commonOption.args ?? [],
7575
} as NodeExecuteOption;
7676
}
77+
78+
const execute = options.execute;
79+
80+
if (execute.type === ExecuteMode.Node && commonOption.experienceEsm) {
81+
if (options.format === 'esm' && !execute.args.includes('--import')) {
82+
execute.args.push('--import', path.join(import.meta.dirname, './import_register.js'));
83+
}
84+
}
7785
}
7886

7987
async function normalizedFormat(config: UserConfig, commonOptions: CommonOptions, options: ResolvedCommonOptions) {
@@ -248,7 +256,7 @@ export class NormalizeOption {
248256

249257
async config(config: UserConfig): Promise<UserConfig> {
250258
await normalizedSimpleConfig(config, this.commonOption, this.options, this.logger);
251-
return withServerOrWatch(
259+
const c = withServerOrWatch(
252260
{
253261
compilation: {
254262
input: this.options.entry,
@@ -263,6 +271,8 @@ export class NormalizeOption {
263271
},
264272
this.options,
265273
);
274+
275+
return c;
266276
}
267277

268278
async normalizeByCommonOption() {

packages/core/src/core/executer.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ExecaChildProcess, execaCommand } from 'execa';
1+
import { type ExecaChildProcess, execaCommand, type Options } from 'execa';
22
import { ExecuteMode, type ExecuteOption, type ResolvedCommonOptions } from '../types/options';
33
import type { Logger } from '@farmfe/core';
44
import { delay } from '../util/async';
@@ -11,31 +11,31 @@ export class Executer {
1111
public option: ExecuteOption,
1212
public logger: Logger,
1313
public normalizedOption: ResolvedCommonOptions,
14-
) { }
14+
) {}
1515

16-
execute(path: string, name: string, logger = this.logger) {
16+
execute(path: string, name: string, logger = this.logger, options: Options = {}) {
1717
switch (this.option.type) {
1818
case ExecuteMode.Browser: {
1919
// console.log('TODO: use open command');
2020
break;
2121
}
2222
case ExecuteMode.Node: {
23-
this._execute('node', name, [path, ...this.option.args], logger);
23+
this._execute('node', name, [...this.option.args, path], logger, options);
2424
break;
2525
}
2626
case ExecuteMode.Custom: {
27-
this._execute(this.option.command, name, [path, ...this.option.args], logger);
27+
this._execute(this.option.command, name, [...this.option.args, path], logger, options);
2828
break;
2929
}
3030
}
3131
}
3232

33-
async _execute(command: string, name: string, args: string[], logger: Logger) {
34-
33+
async _execute(command: string, name: string, args: string[], logger: Logger, options: Options = {}) {
3534
if (this.child) {
3635
await this.closeChild();
3736
}
3837
const child = execaCommand([command, ...args].join(' '), {
38+
...options,
3939
cwd: process.cwd(),
4040
stdio: 'pipe',
4141
});

packages/core/src/core/ipc/client.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import EventEmitter from 'node:events';
2+
import net from 'node:net';
3+
4+
export class IpcClient<S, R> {
5+
private client!: net.Socket;
6+
7+
events = new EventEmitter();
8+
9+
start(socketPath: string) {
10+
const client = net.createConnection(socketPath);
11+
12+
client.on('data', (data) => {
13+
this.events.emit('data', data);
14+
});
15+
16+
this.client = client;
17+
}
18+
19+
send(data: S) {
20+
this.client.write(JSON.stringify(data));
21+
}
22+
23+
onMessage(callback: (data: R) => void) {
24+
this.events.on('data', (data) => {
25+
callback(JSON.parse(data.toString()));
26+
});
27+
}
28+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum ServiceStatus {
2+
WaitShakeHand = 1,
3+
Ready = 2,
4+
}

packages/core/src/core/ipc/server.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import tmp from 'tmp';
2+
import net from 'node:net';
3+
import { Service } from './service';
4+
import EventEmitter from 'node:events';
5+
import path from 'node:path';
6+
7+
interface TempFileResult {
8+
path: string;
9+
cleanupCallback: () => void;
10+
}
11+
12+
function createTempFile(): Promise<TempFileResult> {
13+
return new Promise((resolve) => {
14+
tmp.dir({}, (_err, name, cleanupCallback) => {
15+
resolve({
16+
path: path.join(name, `socket-${Date.now()}`),
17+
cleanupCallback,
18+
});
19+
});
20+
});
21+
}
22+
23+
export class IpcServer<S, R> {
24+
private sockets = new Set<Service<S, R>>();
25+
private events = new EventEmitter();
26+
private _server!: net.Server;
27+
socket_path!: string;
28+
29+
async start() {
30+
const tempFile = await createTempFile();
31+
this.socket_path = tempFile.path;
32+
33+
const server = net.createServer((socket) => {
34+
const service = new Service<S, R>(socket);
35+
socket.on('close', () => {
36+
this.sockets.delete(service);
37+
});
38+
this.sockets.add(service);
39+
this.events.emit('connection', service);
40+
});
41+
42+
server.listen(tempFile);
43+
44+
this._server = server;
45+
}
46+
47+
onConnection(callback: (socket: Service<S, R>) => void) {
48+
const handler = (socket: Service<S, R>) => {
49+
callback(socket);
50+
51+
socket.onClose(() => {
52+
this.events.off('connection', handler);
53+
});
54+
};
55+
this.events.on('connection', handler);
56+
}
57+
58+
close() {
59+
this._server.close();
60+
this.sockets.clear();
61+
}
62+
63+
send(data: S) {
64+
for (const socket of this.sockets) {
65+
socket.send(data);
66+
}
67+
}
68+
}

packages/core/src/core/ipc/service.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { Socket } from 'node:net';
2+
3+
export class Service<S, R> {
4+
// private status = ServiceStatus.WaitShakeHand;
5+
6+
constructor(private _socket: Socket) {}
7+
8+
send(data: S) {
9+
if (this._socket.closed) {
10+
return;
11+
}
12+
this._socket.write(JSON.stringify(data));
13+
}
14+
15+
onMessage(callback: (data: R) => void) {
16+
const handler = (data: Buffer) => {
17+
callback(JSON.parse(data.toString()));
18+
};
19+
20+
this._socket.on('data', handler);
21+
22+
return () => {
23+
this._socket.off('data', handler);
24+
};
25+
}
26+
27+
close() {
28+
this._socket.end();
29+
}
30+
31+
onClose(callback: () => void) {
32+
this._socket.on('close', callback);
33+
}
34+
35+
get isClose() {
36+
return this._socket.closed;
37+
}
38+
}

packages/core/src/core/proxyCompiler/index.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Compiler } from '@farmfe/core';
22
import EventEmitter from 'node:events';
3-
import { proxyCompilerFn } from './util';
3+
import { proxyCompilerFn, defineProperty } from './util';
44
import type { FnContext, OmitFnReturn } from './interface';
55

66
export class ProxyCompiler {
@@ -10,6 +10,7 @@ export class ProxyCompiler {
1010

1111
private _preProxyFnList: (keyof Compiler)[] = [];
1212
private alreadyProxyFnList: Set<keyof Compiler> = new Set();
13+
isDisableEmit = false;
1314

1415
start(compiler: Compiler) {
1516
const isRestart = !!this.compiler;
@@ -32,13 +33,13 @@ export class ProxyCompiler {
3233
}
3334
}
3435

35-
private proxyCompiler<K extends keyof Compiler>(fnName: K) {
36+
private proxyCompiler<K extends keyof Compiler>(fnName: K, force = false) {
3637
if (!this.compiler) {
3738
this._preProxyFnList.push(fnName);
3839
return;
3940
}
4041

41-
if (this.alreadyProxyFnList.has(fnName)) {
42+
if (!force && this.alreadyProxyFnList.has(fnName)) {
4243
return;
4344
}
4445

@@ -48,6 +49,12 @@ export class ProxyCompiler {
4849
proxyCompilerFn(this.compiler, fnName, (...args: any[]) => this.event.emit(fnName, ...args));
4950
}
5051

52+
private replaceCompiler<K extends keyof Compiler>(fnName: K, fn: Compiler[K]) {
53+
defineProperty(this.compiler, fnName, fn);
54+
// re proxy the compiler
55+
this.proxyCompiler(fnName, true);
56+
}
57+
5158
private on<
5259
T extends Compiler,
5360
K extends keyof OFT,
@@ -67,4 +74,15 @@ export class ProxyCompiler {
6774
onWriteResourcesToDisk(handler: OmitFnReturn<Compiler['writeResourcesToDisk']>) {
6875
return this.on('writeResourcesToDisk', (r) => handler(...r.args));
6976
}
77+
78+
disableEmit() {
79+
if (this.isDisableEmit) return;
80+
const handler = () => Promise.resolve();
81+
this.replaceCompiler('writeResourcesToDisk', handler);
82+
this.isDisableEmit = true;
83+
}
84+
85+
resources() {
86+
return this.compiler.resources();
87+
}
7088
}

packages/core/src/core/proxyCompiler/util.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ export function defineProperty<O, K extends keyof O, V extends O[K]>(obj: O, key
55

66
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
77
Object.defineProperty(obj, key, {
8+
writable: true,
9+
enumerable: true,
10+
configurable: true,
811
value,
9-
...descriptor,
1012
});
1113

1214
return origin as V;
@@ -21,7 +23,7 @@ export function proxyCompilerFn<
2123
},
2224
K extends keyof OFT = keyof OFT,
2325
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
24-
F extends (...args: any) => any = OFT[K]
26+
F extends (...args: any) => any = OFT[K],
2527
>(compiler: T, fnName: K, callback: F) {
2628
const handler = ((...args: Parameters<OFT[K]>) => {
2729
const r = origin.bind(compiler)(...args);

0 commit comments

Comments
 (0)