zero-com 0.0.4 → 0.0.5
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/README.md +308 -32
- package/index.js +14 -120
- package/index.ts +1 -154
- package/lib/common.js +9 -0
- package/lib/common.ts +14 -0
- package/lib/index.js +18 -0
- package/lib/index.ts +19 -0
- package/lib/rollup.js +154 -0
- package/lib/rollup.ts +155 -0
- package/lib/webpack.js +146 -0
- package/lib/webpack.ts +167 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -2,28 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
It is a zero-byte no-lib utility for transparently communicating client-side and server-side modules residing in the same full-stack project.
|
|
4
4
|
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Usage](#usage)
|
|
8
|
+
- [Transport layer](#transport-layer)
|
|
9
|
+
- [Context](#context)
|
|
10
|
+
- [Plugin options](#plugin-options)
|
|
11
|
+
- [Complete Example](#complete-example)
|
|
12
|
+
|
|
5
13
|
## Usage
|
|
6
14
|
|
|
7
|
-
Webpack
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
})
|
|
15
|
+
Zero-com can be used with either Webpack or Rollup.
|
|
16
|
+
|
|
17
|
+
### Webpack
|
|
18
|
+
|
|
19
|
+
To use Zero-com with Webpack, you need to add the `ZeroComWebpackPlugin` to your `webpack.config.js` file.
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
// webpack.config.js
|
|
23
|
+
const { ZeroComWebpackPlugin } = require('zero-com/webpack');
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
// ... your webpack config
|
|
27
|
+
plugins: [
|
|
28
|
+
new ZeroComWebpackPlugin({
|
|
29
|
+
development: true,
|
|
30
|
+
patterns: {
|
|
31
|
+
client: 'src/client/**',
|
|
32
|
+
server: 'src/server/api/**',
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
};
|
|
16
37
|
```
|
|
17
38
|
|
|
18
|
-
|
|
39
|
+
### Rollup
|
|
40
|
+
|
|
41
|
+
To use Zero-com with Rollup, you need to add the `zeroComRollupPlugin` to your `rollup.config.js` file.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// rollup.config.js
|
|
45
|
+
import zeroComRollupPlugin from 'zero-com/rollup';
|
|
46
|
+
|
|
47
|
+
export default {
|
|
48
|
+
// ... your rollup config
|
|
49
|
+
plugins: [
|
|
50
|
+
zeroComRollupPlugin({
|
|
51
|
+
development: true,
|
|
52
|
+
patterns: {
|
|
53
|
+
client: 'src/client/**',
|
|
54
|
+
server: 'src/server/api/**',
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The above code will identify all the references from client-side code to the server-side files and will tranform the modules to comunicate through your defined transport layer. The only callable functions in the server-side modules will be the exported async functions. See the example below.
|
|
19
62
|
|
|
20
63
|
Server side
|
|
21
64
|
```js
|
|
22
65
|
// server/phones.ts
|
|
23
|
-
// callable
|
|
24
66
|
export async function getPhones() { }
|
|
25
67
|
|
|
26
|
-
//
|
|
68
|
+
// or
|
|
27
69
|
export const getPhones = async () => { }
|
|
28
70
|
```
|
|
29
71
|
|
|
@@ -33,30 +75,264 @@ Client side
|
|
|
33
75
|
import { getPhones } '../server/phones'
|
|
34
76
|
```
|
|
35
77
|
|
|
36
|
-
##
|
|
37
|
-
Zero-com does not define any transport layer, it allows you to define a new one or reuse your own.
|
|
78
|
+
## Transport layer
|
|
38
79
|
|
|
39
|
-
-
|
|
40
|
-
- `global.ZERO_COM_SERVER_REGISTRY` object available on the server-side of which the keys are the name of the methods and the values are the functions to be executed.
|
|
80
|
+
Zero-com does not define any transport layer, it is up to you to create one or reuse your own. This means you have complete control over how data is sent between the client and server.
|
|
41
81
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
82
|
+
### Communication Flow
|
|
83
|
+
|
|
84
|
+
The following diagram illustrates the communication flow between the client and server:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
+--------+ +-----------------------------+ +-------------+
|
|
88
|
+
| Client |----->| window.ZERO_COM_CLIENT_SEND |----->| Your Server |
|
|
89
|
+
+--------+ +-----------------------------+ +-------------+
|
|
90
|
+
|
|
|
91
|
+
v
|
|
92
|
+
+--------+ +-------------------------+ +-------------------+
|
|
93
|
+
| Client |<-----| (Your custom transport) |<-----| someCustomHandler |
|
|
94
|
+
+--------+ +-------------------------+ +-------------------+
|
|
48
95
|
```
|
|
49
96
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
97
|
+
### Client-side
|
|
98
|
+
|
|
99
|
+
All messages from the client-side will be sent using the `window.ZERO_COM_CLIENT_SEND` function. You need to define this function in your client-side code.
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// client/transport.js
|
|
103
|
+
window.ZERO_COM_CLIENT_SEND = async ({ funcId, params }) => {
|
|
104
|
+
const response = await fetch('http://localhost:8000/api', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({ funcId, params }),
|
|
110
|
+
});
|
|
111
|
+
return await response.json();
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Server-side
|
|
116
|
+
|
|
117
|
+
On the server-side, you need to create a handler that receives messages from the client, executes the corresponding function, and returns the result. The `global.ZERO_COM_SERVER_REGISTRY` object contains all the server functions that can be called from the client.
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
// server/api.js
|
|
121
|
+
import { execServerFn } from 'zero-com';
|
|
122
|
+
|
|
123
|
+
const someCustomHandler = async (message) => {
|
|
124
|
+
const func = global.ZERO_COM_SERVER_REGISTRY[message.funcId];
|
|
125
|
+
if (func) {
|
|
126
|
+
return await execServerFn(func, message.params);
|
|
127
|
+
} else {
|
|
128
|
+
throw new Error(`Function with id ${message.funcId} not found.`);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Example of how to use the handler with an Express server
|
|
133
|
+
import express from 'express';
|
|
134
|
+
|
|
135
|
+
const app = express();
|
|
136
|
+
app.use(express.json());
|
|
137
|
+
|
|
138
|
+
app.post('/api', async (req, res) => {
|
|
139
|
+
try {
|
|
140
|
+
const result = await someCustomHandler(req.body);
|
|
141
|
+
res.json(result);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
res.status(500).json({ error: error.message });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
app.listen(8000, () => {
|
|
148
|
+
console.log('Server running at http://localhost:8000/');
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Context
|
|
153
|
+
|
|
154
|
+
Often you want to pass a context-related object to the server functions to have access to data like the request, response, session, etc. Zero-com provides a simple way to do this.
|
|
155
|
+
|
|
156
|
+
### Passing Context to Server Functions
|
|
157
|
+
|
|
158
|
+
To pass context to a server function, you need to wrap the function in `serverFn` and receive the context as the first parameter.
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
// server/api/phones.js
|
|
162
|
+
import { serverFn } from 'zero-com';
|
|
163
|
+
|
|
164
|
+
export const getPhones = serverFn(async (ctx, name) => {
|
|
165
|
+
// ctx is the context object passed from the server
|
|
166
|
+
console.log(ctx.request.headers);
|
|
167
|
+
// ... your code
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Providing Context on the Server
|
|
172
|
+
|
|
173
|
+
You can pass the context to `execServerFn` when you execute the server function.
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
// server/api.js
|
|
177
|
+
import { execServerFn } from 'zero-com';
|
|
178
|
+
|
|
179
|
+
const myHandler = (request, response, message) => {
|
|
180
|
+
const ctx = { request, response };
|
|
181
|
+
const func = global.ZERO_COM_SERVER_REGISTRY[message.funcId];
|
|
182
|
+
// pass context on exec
|
|
183
|
+
return execServerFn(func, ctx, message.params);
|
|
184
|
+
};
|
|
56
185
|
```
|
|
57
186
|
|
|
58
187
|
## Plugin options
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
188
|
+
|
|
189
|
+
| Option | Type | Description |
|
|
190
|
+
|-------------|-----------|-----------------------------------------------------------------------------|
|
|
191
|
+
| `development` | `boolean` | If `false`, will add internal variable renaming to the final bundle. |
|
|
192
|
+
| `patterns` | `object` | |
|
|
193
|
+
| `patterns.client` | `string` | A glob pattern to identify client-side files. |
|
|
194
|
+
| `patterns.server` | `string` | A glob pattern to identify server-side files. |
|
|
195
|
+
|
|
196
|
+
## Complete Example
|
|
197
|
+
|
|
198
|
+
Here's a complete example of how to use Zero-com in a project.
|
|
199
|
+
|
|
200
|
+
### Project Structure
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
.
|
|
204
|
+
├── package.json
|
|
205
|
+
├── webpack.config.js
|
|
206
|
+
├── rollup.config.js
|
|
207
|
+
└── src
|
|
208
|
+
├── client
|
|
209
|
+
│ ├── index.js
|
|
210
|
+
│ └── transport.js
|
|
211
|
+
└── server
|
|
212
|
+
└── api
|
|
213
|
+
└── phones.js
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Client-side
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
// src/client/index.js
|
|
220
|
+
import { getPhones } from '../../server/api/phones';
|
|
221
|
+
|
|
222
|
+
async function main() {
|
|
223
|
+
const phones = await getPhones('iPhone');
|
|
224
|
+
console.log(phones);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main();
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
// src/client/transport.js
|
|
232
|
+
window.ZERO_COM_CLIENT_SEND = async ({ funcId, params }) => {
|
|
233
|
+
const response = await fetch('http://localhost:8000/api', {
|
|
234
|
+
method: 'POST',
|
|
235
|
+
headers: {
|
|
236
|
+
'Content-Type': 'application/json',
|
|
237
|
+
},
|
|
238
|
+
body: JSON.stringify({ funcId, params }),
|
|
239
|
+
});
|
|
240
|
+
return await response.json();
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Server-side
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
// src/server/api/phones.js
|
|
248
|
+
import { serverFn } from 'zero-com';
|
|
249
|
+
|
|
250
|
+
export const getPhones = serverFn(async (ctx, name) => {
|
|
251
|
+
// In a real application, you would fetch this from a database
|
|
252
|
+
const allPhones = [
|
|
253
|
+
{ name: 'iPhone 13', brand: 'Apple' },
|
|
254
|
+
{ name: 'Galaxy S22', brand: 'Samsung' },
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
return allPhones.filter((phone) => phone.name.includes(name));
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Server
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// server.js
|
|
265
|
+
import express from 'express';
|
|
266
|
+
import { execServerFn } from 'zero-com';
|
|
267
|
+
import './src/server/api/phones.js'; // Make sure to import the server-side modules
|
|
268
|
+
|
|
269
|
+
const app = express();
|
|
270
|
+
app.use(express.json());
|
|
271
|
+
|
|
272
|
+
app.post('/api', async (req, res) => {
|
|
273
|
+
const { funcId, params } = req.body;
|
|
274
|
+
const func = global.ZERO_COM_SERVER_REGISTRY[funcId];
|
|
275
|
+
|
|
276
|
+
if (func) {
|
|
277
|
+
try {
|
|
278
|
+
const result = await execServerFn(func, { req, res }, params);
|
|
279
|
+
res.json(result);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
res.status(500).json({ error: error.message });
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
res.status(404).json({ error: `Function with id ${funcId} not found.` });
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
app.listen(8000, () => {
|
|
289
|
+
console.log('Server running at http://localhost:8000/');
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Webpack Configuration
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
// webpack.config.js
|
|
297
|
+
const { ZeroComWebpackPlugin } = require('zero-com/webpack');
|
|
298
|
+
|
|
299
|
+
module.exports = {
|
|
300
|
+
mode: 'development',
|
|
301
|
+
entry: './src/client/index.js',
|
|
302
|
+
output: {
|
|
303
|
+
filename: 'main.js',
|
|
304
|
+
path: __dirname + '/dist',
|
|
305
|
+
},
|
|
306
|
+
plugins: [
|
|
307
|
+
new ZeroComWebpackPlugin({
|
|
308
|
+
patterns: {
|
|
309
|
+
client: 'src/client/**',
|
|
310
|
+
server: 'src/server/api/**',
|
|
311
|
+
},
|
|
312
|
+
}),
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Rollup Configuration
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
// rollup.config.js
|
|
321
|
+
import zeroComRollupPlugin from 'zero-com/rollup';
|
|
322
|
+
|
|
323
|
+
export default {
|
|
324
|
+
input: 'src/client/index.js',
|
|
325
|
+
output: {
|
|
326
|
+
file: 'dist/main.js',
|
|
327
|
+
format: 'cjs',
|
|
328
|
+
},
|
|
329
|
+
plugins: [
|
|
330
|
+
zeroComRollupPlugin({
|
|
331
|
+
patterns: {
|
|
332
|
+
client: 'src/client/**',
|
|
333
|
+
server: 'src/server/api/**',
|
|
334
|
+
},
|
|
335
|
+
}),
|
|
336
|
+
],
|
|
337
|
+
};
|
|
338
|
+
```
|
package/index.js
CHANGED
|
@@ -1,123 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const minimatch_1 = require("minimatch");
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const ts_morph_1 = require("ts-morph");
|
|
11
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
-
class ZeroComWebpackPlugin {
|
|
13
|
-
constructor(options) {
|
|
14
|
-
this.options = Object.assign(Object.assign({ development: true }, options), { patterns: Object.assign({}, options.patterns) });
|
|
15
|
-
this.compilationId = String(Math.floor(Math.random() * 1000000));
|
|
16
|
-
this.clientPattern = this.options.patterns.client;
|
|
17
|
-
this.serverPattern = this.options.patterns.server;
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
7
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
nmf.hooks.beforeResolve.tap(pluginName, (resolveData) => {
|
|
27
|
-
const absolutePath = path_1.default.resolve(resolveData.context, resolveData.request);
|
|
28
|
-
const isServerFile = (0, minimatch_1.minimatch)(absolutePath, path_1.default.join(compiler.context, this.serverPattern));
|
|
29
|
-
if (!isServerFile)
|
|
30
|
-
return;
|
|
31
|
-
const requestedFromClient = (0, minimatch_1.minimatch)(resolveData.contextInfo.issuer, path_1.default.join(compiler.context, this.clientPattern));
|
|
32
|
-
const tsPath = absolutePath + '.ts';
|
|
33
|
-
const jsPath = absolutePath + '.js';
|
|
34
|
-
const mjsPath = absolutePath + '.mjs';
|
|
35
|
-
let resolvedPath = '';
|
|
36
|
-
if (fs_1.default.existsSync(tsPath)) {
|
|
37
|
-
resolvedPath = tsPath;
|
|
38
|
-
}
|
|
39
|
-
else if (fs_1.default.existsSync(jsPath)) {
|
|
40
|
-
resolvedPath = jsPath;
|
|
41
|
-
}
|
|
42
|
-
else if (fs_1.default.existsSync(mjsPath)) {
|
|
43
|
-
resolvedPath = mjsPath;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
throw new Error('Unable to resolve: ' + absolutePath);
|
|
47
|
-
}
|
|
48
|
-
const originalContent = fs_1.default.readFileSync(resolvedPath, 'utf8');
|
|
49
|
-
const project = new ts_morph_1.Project({
|
|
50
|
-
compilerOptions: {
|
|
51
|
-
target: typescript_1.default.ScriptTarget.ES2017,
|
|
52
|
-
module: typescript_1.default.ModuleKind.ESNext,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
const sourceFile = project.createSourceFile(absolutePath, originalContent, { overwrite: true });
|
|
56
|
-
let newModuleContent = '';
|
|
57
|
-
if (requestedFromClient) {
|
|
58
|
-
const generatedFunctions = [];
|
|
59
|
-
sourceFile.getFunctions().forEach(func => {
|
|
60
|
-
if (func.isExported() && func.isAsync()) {
|
|
61
|
-
const funcName = String(func.getName());
|
|
62
|
-
const lineNumber = func.getStartLineNumber();
|
|
63
|
-
const funcParams = func.getParameters().map(p => p.getName()).join(', ');
|
|
64
|
-
const method = formatMethodName(funcName, path_1.default.relative(compiler.context, absolutePath), lineNumber);
|
|
65
|
-
const newFunctionBody = `return window.${ZERO_COM_CLIENT_SEND}({method: '${method}', params: [${funcParams}]})`;
|
|
66
|
-
func.setBodyText(newFunctionBody);
|
|
67
|
-
generatedFunctions.push(func.getText());
|
|
68
|
-
console.log('client:', method);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
newModuleContent = generatedFunctions.join('\n\n');
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
const chunks = [];
|
|
75
|
-
sourceFile.getFunctions().forEach(func => {
|
|
76
|
-
if (func.isExported() && func.isAsync()) {
|
|
77
|
-
const funcName = String(func.getName());
|
|
78
|
-
const lineNumber = func.getStartLineNumber();
|
|
79
|
-
const method = formatMethodName(funcName, path_1.default.relative(compiler.context, absolutePath), lineNumber);
|
|
80
|
-
chunks.push(`global.${ZERO_COM_SERVER_REGISTRY}['${method}'] = ${funcName}`);
|
|
81
|
-
console.log('server:', method);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
newModuleContent = `${originalContent} if (!global.${ZERO_COM_SERVER_REGISTRY}) global.${ZERO_COM_SERVER_REGISTRY} = Object.create(null); ${chunks.join(',')}`;
|
|
85
|
-
}
|
|
86
|
-
project.createSourceFile(absolutePath + '.ts', newModuleContent, { overwrite: true });
|
|
87
|
-
const result = project.emitToMemory();
|
|
88
|
-
const newContent = result.getFiles()[0].text;
|
|
89
|
-
const inlineLoader = `data:text/javascript,${encodeURIComponent(newContent)}`;
|
|
90
|
-
resolveData.request = inlineLoader;
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
if (this.options.development)
|
|
94
|
-
return;
|
|
95
|
-
const replacements = [
|
|
96
|
-
{ target: ZERO_COM_CLIENT_SEND, replacement: `__ZERO_COM_CLIENT_SEND_${this.compilationId}` },
|
|
97
|
-
{ target: ZERO_COM_SERVER_REGISTRY, replacement: `__ZERO_COM_SERVER_REGISTRY_${this.compilationId}` }
|
|
98
|
-
];
|
|
99
|
-
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
|
100
|
-
compilation.hooks.processAssets.tap({ name: pluginName, stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE }, (assets) => {
|
|
101
|
-
for (const assetName in assets) {
|
|
102
|
-
if (assetName.endsWith('.js')) {
|
|
103
|
-
let assetSource = String(assets[assetName].source());
|
|
104
|
-
let modified = false;
|
|
105
|
-
replacements.forEach(({ target, replacement }) => {
|
|
106
|
-
if (assetSource.includes(target)) {
|
|
107
|
-
assetSource = assetSource.replaceAll(target, replacement);
|
|
108
|
-
modified = true;
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
if (modified) {
|
|
112
|
-
compilation.updateAsset(assetName, new RawSource(assetSource));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
exports.ZeroComWebpackPlugin = ZeroComWebpackPlugin;
|
|
121
|
-
const formatMethodName = (funcName, path, line) => {
|
|
122
|
-
return `${funcName}@${path}:${line}`;
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
123
15
|
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./lib/index"), exports);
|
package/index.ts
CHANGED
|
@@ -1,154 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { minimatch } from 'minimatch'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { Project } from 'ts-morph'
|
|
5
|
-
import ts from 'typescript'
|
|
6
|
-
import { Compiler } from 'webpack'
|
|
7
|
-
|
|
8
|
-
type Options = {
|
|
9
|
-
development?: boolean,
|
|
10
|
-
patterns: {
|
|
11
|
-
client: string,
|
|
12
|
-
server: string,
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type Message = {
|
|
17
|
-
method: string,
|
|
18
|
-
params: any[],
|
|
19
|
-
[key: string]: any
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class ZeroComWebpackPlugin {
|
|
23
|
-
private options: Options
|
|
24
|
-
private compilationId: string
|
|
25
|
-
private clientPattern: string
|
|
26
|
-
private serverPattern: string
|
|
27
|
-
|
|
28
|
-
constructor(options: Options) {
|
|
29
|
-
this.options = {
|
|
30
|
-
development: true,
|
|
31
|
-
...options,
|
|
32
|
-
patterns: {
|
|
33
|
-
...options.patterns
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
this.compilationId = String(Math.floor(Math.random() * 1000000))
|
|
37
|
-
this.clientPattern = this.options.patterns.client
|
|
38
|
-
this.serverPattern = this.options.patterns.server
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
apply(compiler: Compiler) {
|
|
42
|
-
const ZERO_COM_CLIENT_SEND = 'ZERO_COM_CLIENT_SEND'
|
|
43
|
-
const ZERO_COM_SERVER_REGISTRY = 'ZERO_COM_SERVER_REGISTRY'
|
|
44
|
-
|
|
45
|
-
const pluginName = ZeroComWebpackPlugin.name
|
|
46
|
-
const { webpack } = compiler
|
|
47
|
-
const { RawSource } = webpack.sources
|
|
48
|
-
|
|
49
|
-
compiler.hooks.normalModuleFactory.tap(pluginName, (nmf) => {
|
|
50
|
-
nmf.hooks.beforeResolve.tap(pluginName, (resolveData) => {
|
|
51
|
-
const absolutePath = path.resolve(resolveData.context, resolveData.request)
|
|
52
|
-
const isServerFile = minimatch(absolutePath, path.join(compiler.context, this.serverPattern))
|
|
53
|
-
if (!isServerFile) return
|
|
54
|
-
|
|
55
|
-
const requestedFromClient = minimatch(resolveData.contextInfo.issuer, path.join(compiler.context, this.clientPattern))
|
|
56
|
-
|
|
57
|
-
const tsPath = absolutePath + '.ts'
|
|
58
|
-
const jsPath = absolutePath + '.js'
|
|
59
|
-
const mjsPath = absolutePath + '.mjs'
|
|
60
|
-
let resolvedPath = ''
|
|
61
|
-
|
|
62
|
-
if (fs.existsSync(tsPath)) {
|
|
63
|
-
resolvedPath = tsPath
|
|
64
|
-
} else if (fs.existsSync(jsPath)) {
|
|
65
|
-
resolvedPath = jsPath
|
|
66
|
-
} else if (fs.existsSync(mjsPath)) {
|
|
67
|
-
resolvedPath = mjsPath
|
|
68
|
-
} else {
|
|
69
|
-
throw new Error('Unable to resolve: ' + absolutePath)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const originalContent = fs.readFileSync(resolvedPath, 'utf8')
|
|
73
|
-
|
|
74
|
-
const project = new Project({
|
|
75
|
-
compilerOptions: {
|
|
76
|
-
target: ts.ScriptTarget.ES2017,
|
|
77
|
-
module: ts.ModuleKind.ESNext,
|
|
78
|
-
},
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const sourceFile = project.createSourceFile(absolutePath, originalContent, { overwrite: true })
|
|
82
|
-
let newModuleContent = ''
|
|
83
|
-
|
|
84
|
-
if (requestedFromClient) {
|
|
85
|
-
const generatedFunctions: string[] = []
|
|
86
|
-
sourceFile.getFunctions().forEach(func => {
|
|
87
|
-
if (func.isExported() && func.isAsync()) {
|
|
88
|
-
const funcName = String(func.getName())
|
|
89
|
-
const lineNumber = func.getStartLineNumber()
|
|
90
|
-
const funcParams = func.getParameters().map(p => p.getName()).join(', ')
|
|
91
|
-
const method = formatMethodName(funcName, path.relative(compiler.context, absolutePath), lineNumber)
|
|
92
|
-
const newFunctionBody = `return window.${ZERO_COM_CLIENT_SEND}({method: '${method}', params: [${funcParams}]})`
|
|
93
|
-
func.setBodyText(newFunctionBody)
|
|
94
|
-
generatedFunctions.push(func.getText())
|
|
95
|
-
console.log('client:', method)
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
newModuleContent = generatedFunctions.join('\n\n')
|
|
99
|
-
} else {
|
|
100
|
-
const chunks: string[] = []
|
|
101
|
-
sourceFile.getFunctions().forEach(func => {
|
|
102
|
-
if (func.isExported() && func.isAsync()) {
|
|
103
|
-
const funcName = String(func.getName())
|
|
104
|
-
const lineNumber = func.getStartLineNumber()
|
|
105
|
-
const method = formatMethodName(funcName, path.relative(compiler.context, absolutePath), lineNumber)
|
|
106
|
-
chunks.push(`global.${ZERO_COM_SERVER_REGISTRY}['${method}'] = ${funcName}`)
|
|
107
|
-
console.log('server:', method)
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
newModuleContent = `${originalContent} if (!global.${ZERO_COM_SERVER_REGISTRY}) global.${ZERO_COM_SERVER_REGISTRY} = Object.create(null); ${chunks.join(',')}`
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
project.createSourceFile(absolutePath + '.ts', newModuleContent, { overwrite: true })
|
|
114
|
-
const result = project.emitToMemory()
|
|
115
|
-
const newContent = result.getFiles()[0].text
|
|
116
|
-
const inlineLoader = `data:text/javascript,${encodeURIComponent(newContent)}`
|
|
117
|
-
resolveData.request = inlineLoader
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
if (this.options.development) return
|
|
122
|
-
|
|
123
|
-
const replacements = [
|
|
124
|
-
{ target: ZERO_COM_CLIENT_SEND, replacement: `__ZERO_COM_CLIENT_SEND_${this.compilationId}` },
|
|
125
|
-
{ target: ZERO_COM_SERVER_REGISTRY, replacement: `__ZERO_COM_SERVER_REGISTRY_${this.compilationId}` }
|
|
126
|
-
]
|
|
127
|
-
|
|
128
|
-
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
|
|
129
|
-
compilation.hooks.processAssets.tap({ name: pluginName, stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE }, (assets) => {
|
|
130
|
-
for (const assetName in assets) {
|
|
131
|
-
if (assetName.endsWith('.js')) {
|
|
132
|
-
let assetSource = String(assets[assetName].source())
|
|
133
|
-
let modified = false
|
|
134
|
-
replacements.forEach(({ target, replacement }) => {
|
|
135
|
-
if (assetSource.includes(target)) {
|
|
136
|
-
assetSource = assetSource.replaceAll(target, replacement)
|
|
137
|
-
modified = true
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
if (modified) {
|
|
141
|
-
compilation.updateAsset(assetName, new RawSource(assetSource))
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const formatMethodName = (funcName: string, path: string, line: number) => {
|
|
153
|
-
return `${funcName}@${path}:${line}`
|
|
154
|
-
}
|
|
1
|
+
export * from './lib/index'
|
package/lib/common.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatFuncIdName = exports.ZERO_COM_SERVER_REGISTRY = exports.ZERO_COM_CLIENT_SEND = void 0;
|
|
4
|
+
exports.ZERO_COM_CLIENT_SEND = 'ZERO_COM_CLIENT_SEND';
|
|
5
|
+
exports.ZERO_COM_SERVER_REGISTRY = 'ZERO_COM_SERVER_REGISTRY';
|
|
6
|
+
const formatFuncIdName = (funcName, path, line) => {
|
|
7
|
+
return `${funcName}@${path}:${line}`;
|
|
8
|
+
};
|
|
9
|
+
exports.formatFuncIdName = formatFuncIdName;
|
package/lib/common.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Options = {
|
|
2
|
+
development?: boolean,
|
|
3
|
+
patterns: {
|
|
4
|
+
client: string,
|
|
5
|
+
server: string,
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const ZERO_COM_CLIENT_SEND = 'ZERO_COM_CLIENT_SEND'
|
|
10
|
+
export const ZERO_COM_SERVER_REGISTRY = 'ZERO_COM_SERVER_REGISTRY'
|
|
11
|
+
|
|
12
|
+
export const formatFuncIdName = (funcName: string, path: string, line: number): string => {
|
|
13
|
+
return `${funcName}@${path}:${line}`
|
|
14
|
+
}
|