@modern-js/plugin-data-loader 0.0.0-next-20221104140607

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/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @modern-js/plugin-data-loader
2
+
3
+ ## 0.0.0-next-20221104140607
4
+
5
+ ### Minor Changes
6
+
7
+ - 543be955: feat: compile server loader and support handle loader request
8
+ feat: 编译 server loader 并支持处理 loader 的请求
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [edd1cfb1]
13
+ - Updated dependencies [cc971eab]
14
+ - Updated dependencies [b8bbe036]
15
+ - Updated dependencies [d5a31df7]
16
+ - Updated dependencies [3bbea92b]
17
+ - Updated dependencies [543be955]
18
+ - @modern-js/utils@0.0.0-next-20221104140607
19
+ - @modern-js/babel-compiler@0.0.0-next-20221104140607
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Modern.js
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+
2
+ <p align="center">
3
+ <a href="https://modernjs.dev" target="blank"><img src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ylaelkeh7nuhfnuhf/modernjs-cover.png" width="300" alt="Modern.js Logo" /></a>
4
+ </p>
5
+ <p align="center">
6
+ 现代 Web 工程体系
7
+ <br/>
8
+ <a href="https://modernjs.dev" target="blank">
9
+ modernjs.dev
10
+ </a>
11
+ </p>
12
+ <p align="center">
13
+ The meta-framework suite designed from scratch for frontend-focused modern web development
14
+ </p>
15
+
16
+ # Introduction
17
+
18
+ > The doc site ([modernjs.dev](https://modernjs.dev)) and articles are only available in Chinese for now, we are planning to add English versions soon.
19
+
20
+ - [Modern.js: Hello, World!](https://zhuanlan.zhihu.com/p/426707646)
21
+
22
+ ## Getting Started
23
+
24
+ - [Quick Start](https://modernjs.dev/docs/start)
25
+ - [Guides](https://modernjs.dev/docs/guides)
26
+ - [API References](https://modernjs.dev/docs/apis)
27
+
28
+ ## Contributing
29
+
30
+ - [Contributing Guide](https://github.com/modern-js-dev/modern.js/blob/main/CONTRIBUTING.md)
@@ -0,0 +1,44 @@
1
+ // eslint-disable-next-line eslint-comments/disable-enable-pair
2
+
3
+ /* eslint-disable node/prefer-global/url */
4
+ // eslint-disable-next-line eslint-comments/disable-enable-pair
5
+
6
+ /* eslint-disable node/no-unsupported-features/node-builtins */
7
+ import { compile } from 'path-to-regexp';
8
+ export const getRequestUrl = ({
9
+ params,
10
+ request,
11
+ routeId
12
+ }) => {
13
+ const url = new URL(request.url);
14
+ const toPath = compile(url.pathname, {
15
+ encode: encodeURIComponent
16
+ });
17
+ const newPathName = toPath(params);
18
+ url.pathname = newPathName;
19
+ url.searchParams.append('_loader', routeId);
20
+ return url;
21
+ };
22
+ export const createRequest = (routeId, method = 'get') => {
23
+ return async ({
24
+ params,
25
+ request
26
+ }) => {
27
+ const url = getRequestUrl({
28
+ params,
29
+ request,
30
+ routeId
31
+ });
32
+
33
+ try {
34
+ const res = await fetch(url, {
35
+ method,
36
+ signal: request.signal
37
+ });
38
+ return res;
39
+ } catch (error) {
40
+ console.error(error);
41
+ throw error;
42
+ }
43
+ };
44
+ };
@@ -0,0 +1,23 @@
1
+ import path from 'path';
2
+ import { getRouteId } from '@modern-js/utils';
3
+ export const generateClient = ({
4
+ basedir,
5
+ filename
6
+ }) => {
7
+ // TODO: loader 的约定变更
8
+ const componentPath = path.join(path.dirname(filename), 'layout.ts');
9
+ const routeId = getRouteId(componentPath, basedir);
10
+ const requestCreatorPath = path.join(__dirname, './create-request').replace('node', 'treeshaking').replace(/\\/g, '/');
11
+ const importCode = `
12
+ import { createRequest } from '${requestCreatorPath}';
13
+ `;
14
+ const requestCode = `
15
+ const loader = createRequest('${routeId}');
16
+ export default loader;
17
+ export { loader };
18
+ `;
19
+ return `
20
+ ${importCode}
21
+ ${requestCode}
22
+ `;
23
+ };
@@ -0,0 +1,21 @@
1
+ // eslint-disable-next-line eslint-comments/disable-enable-pair
2
+
3
+ /* eslint-disable @babel/no-invalid-this */
4
+ import { generateClient } from "./generate-client";
5
+ export default async function loader(source) {
6
+ var _this$_compiler;
7
+
8
+ this.cacheable();
9
+ const target = (_this$_compiler = this._compiler) === null || _this$_compiler === void 0 ? void 0 : _this$_compiler.options.target;
10
+
11
+ if (target === 'node') {
12
+ return source;
13
+ }
14
+
15
+ const options = this.getOptions();
16
+ const code = generateClient({
17
+ basedir: options.routesDir,
18
+ filename: this.resourcePath
19
+ });
20
+ return code;
21
+ }
@@ -0,0 +1,217 @@
1
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
2
+
3
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
4
+
5
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
+
7
+ import { installGlobals, writeReadableStreamToWritable, Response as NodeResponse } from '@remix-run/node';
8
+ import { matchRoutes } from 'react-router-dom';
9
+ // Polyfill Web Fetch API
10
+ installGlobals();
11
+ const LOADER_SEARCH_PARAM = '_loader';
12
+ const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
13
+ export function isRedirectResponse(response) {
14
+ return redirectStatusCodes.has(response.status);
15
+ }
16
+ export function isResponse(value) {
17
+ return value != null && typeof value.status === 'number' && typeof value.statusText === 'string' && typeof value.headers === 'object' && typeof value.body !== 'undefined';
18
+ }
19
+
20
+ const json = (data, init = {}) => {
21
+ const responseInit = typeof init === 'number' ? {
22
+ status: init
23
+ } : init;
24
+ const headers = new Headers(responseInit.headers);
25
+
26
+ if (!headers.has('Content-Type')) {
27
+ headers.set('Content-Type', 'application/json; charset=utf-8');
28
+ }
29
+
30
+ return new NodeResponse(JSON.stringify(data), _objectSpread(_objectSpread({}, responseInit), {}, {
31
+ headers
32
+ }));
33
+ }; // TODO: 添加 context
34
+
35
+
36
+ const callRouteLoader = async ({
37
+ routeId,
38
+ loader,
39
+ params,
40
+ request,
41
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
42
+ loadContext
43
+ }) => {
44
+ if (!loader) {
45
+ throw new Error(`You made a ${request.method} request to ${request.url} but did not provide ` + `a default component or \`loader\` for route "${routeId}", ` + `so there is no way to handle the request.`);
46
+ }
47
+
48
+ let result;
49
+
50
+ try {
51
+ result = await loader({
52
+ request,
53
+ params
54
+ });
55
+ } catch (error) {
56
+ if (!isResponse(error)) {
57
+ throw error;
58
+ }
59
+
60
+ result = error;
61
+ }
62
+
63
+ if (result === undefined) {
64
+ throw new Error(`You defined a loader for route "${routeId}" but didn't return ` + `anything from your \`loader\` function. Please return a value or \`null\`.`);
65
+ }
66
+
67
+ return isResponse(result) ? result : json(result);
68
+ };
69
+
70
+ const createLoaderHeaders = requestHeaders => {
71
+ const headers = new Headers();
72
+
73
+ for (const [key, values] of Object.entries(requestHeaders)) {
74
+ if (values) {
75
+ if (Array.isArray(values)) {
76
+ for (const value of values) {
77
+ headers.append(key, value);
78
+ }
79
+ } else {
80
+ headers.set(key, values);
81
+ }
82
+ }
83
+ }
84
+
85
+ return headers;
86
+ };
87
+
88
+ const createLoaderRequest = context => {
89
+ const origin = `${context.protocol}://${context.host}`; // eslint-disable-next-line node/no-unsupported-features/node-builtins, node/prefer-global/url
90
+
91
+ const url = new URL(context.url, origin);
92
+ const controller = new AbortController();
93
+ const init = {
94
+ method: context.method,
95
+ headers: createLoaderHeaders(context.headers),
96
+ signal: controller.signal
97
+ };
98
+ return new Request(url.href, init);
99
+ };
100
+
101
+ const sendLoaderResponse = async (res, nodeResponse) => {
102
+ res.statusMessage = nodeResponse.statusText;
103
+ res.statusCode = nodeResponse.status;
104
+
105
+ for (const [key, value] of nodeResponse.headers.entries()) {
106
+ res.setHeader(key, value);
107
+ }
108
+
109
+ if (nodeResponse.body) {
110
+ await writeReadableStreamToWritable(nodeResponse.body, res);
111
+ } else {
112
+ res.end();
113
+ }
114
+ };
115
+
116
+ export const getPathWithoutEntry = (pathname, entryPath) => {
117
+ if (entryPath === '/') {
118
+ return pathname;
119
+ }
120
+
121
+ return pathname.replace(entryPath, '');
122
+ };
123
+
124
+ const matchEntry = (pathname, entries) => {
125
+ return entries.find(entry => pathname.startsWith(entry.urlPath));
126
+ };
127
+
128
+ export const handleRequest = async ({
129
+ context,
130
+ serverRoutes,
131
+ distDir
132
+ }) => {
133
+ const {
134
+ method,
135
+ query
136
+ } = context;
137
+ const routeId = query[LOADER_SEARCH_PARAM];
138
+
139
+ if (!routeId || method.toLowerCase() !== 'get') {
140
+ return;
141
+ }
142
+
143
+ const entry = matchEntry(context.path, serverRoutes);
144
+
145
+ if (!entry) {
146
+ return;
147
+ }
148
+
149
+ const {
150
+ routes
151
+ } = await import(`${distDir}/loader-routes/${entry.entryName}`);
152
+
153
+ if (!routes) {
154
+ return;
155
+ }
156
+
157
+ const {
158
+ res
159
+ } = context;
160
+ const pathname = getPathWithoutEntry(context.path, entry.urlPath);
161
+ const matches = matchRoutes(routes, pathname);
162
+
163
+ if (!matches) {
164
+ res.statusCode = 403;
165
+ res.end(`Route ${pathname} was not matched`);
166
+ return;
167
+ }
168
+
169
+ const match = matches === null || matches === void 0 ? void 0 : matches.find(match => match.route.id === routeId);
170
+
171
+ if (!match) {
172
+ res.statusCode = 403;
173
+ res.end(`Route ${routeId} does not match URL ${context.path}`);
174
+ return;
175
+ }
176
+
177
+ const request = createLoaderRequest(context);
178
+ let response;
179
+
180
+ try {
181
+ response = await callRouteLoader({
182
+ loader: match.route.loader,
183
+ routeId: match.route.id,
184
+ params: match.params,
185
+ request,
186
+ loadContext: {}
187
+ }); // TODO: 处理 redirect
188
+ } catch (error) {
189
+ const message = String(error);
190
+ response = new NodeResponse(message, {
191
+ status: 500,
192
+ headers: {
193
+ 'Content-Type': 'text/plain'
194
+ }
195
+ });
196
+ }
197
+
198
+ sendLoaderResponse(res, response);
199
+ };
200
+ export default (() => ({
201
+ name: '@modern-js/plugin-data-loader',
202
+ setup: () => ({
203
+ preparebeforeRouteHandler({
204
+ serverRoutes,
205
+ distDir
206
+ }) {
207
+ return async context => {
208
+ return handleRequest({
209
+ serverRoutes,
210
+ distDir,
211
+ context
212
+ });
213
+ };
214
+ }
215
+
216
+ })
217
+ }));
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getRequestUrl = exports.createRequest = void 0;
7
+
8
+ var _pathToRegexp = require("path-to-regexp");
9
+
10
+ // eslint-disable-next-line eslint-comments/disable-enable-pair
11
+
12
+ /* eslint-disable node/prefer-global/url */
13
+ // eslint-disable-next-line eslint-comments/disable-enable-pair
14
+
15
+ /* eslint-disable node/no-unsupported-features/node-builtins */
16
+ const getRequestUrl = ({
17
+ params,
18
+ request,
19
+ routeId
20
+ }) => {
21
+ const url = new URL(request.url);
22
+ const toPath = (0, _pathToRegexp.compile)(url.pathname, {
23
+ encode: encodeURIComponent
24
+ });
25
+ const newPathName = toPath(params);
26
+ url.pathname = newPathName;
27
+ url.searchParams.append('_loader', routeId);
28
+ return url;
29
+ };
30
+
31
+ exports.getRequestUrl = getRequestUrl;
32
+
33
+ const createRequest = (routeId, method = 'get') => {
34
+ return async ({
35
+ params,
36
+ request
37
+ }) => {
38
+ const url = getRequestUrl({
39
+ params,
40
+ request,
41
+ routeId
42
+ });
43
+
44
+ try {
45
+ const res = await fetch(url, {
46
+ method,
47
+ signal: request.signal
48
+ });
49
+ return res;
50
+ } catch (error) {
51
+ console.error(error);
52
+ throw error;
53
+ }
54
+ };
55
+ };
56
+
57
+ exports.createRequest = createRequest;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.generateClient = void 0;
7
+
8
+ var _path = _interopRequireDefault(require("path"));
9
+
10
+ var _utils = require("@modern-js/utils");
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ const generateClient = ({
15
+ basedir,
16
+ filename
17
+ }) => {
18
+ // TODO: loader 的约定变更
19
+ const componentPath = _path.default.join(_path.default.dirname(filename), 'layout.ts');
20
+
21
+ const routeId = (0, _utils.getRouteId)(componentPath, basedir);
22
+
23
+ const requestCreatorPath = _path.default.join(__dirname, './create-request').replace('node', 'treeshaking').replace(/\\/g, '/');
24
+
25
+ const importCode = `
26
+ import { createRequest } from '${requestCreatorPath}';
27
+ `;
28
+ const requestCode = `
29
+ const loader = createRequest('${routeId}');
30
+ export default loader;
31
+ export { loader };
32
+ `;
33
+ return `
34
+ ${importCode}
35
+ ${requestCode}
36
+ `;
37
+ };
38
+
39
+ exports.generateClient = generateClient;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = loader;
7
+
8
+ var _generateClient = require("./generate-client");
9
+
10
+ // eslint-disable-next-line eslint-comments/disable-enable-pair
11
+
12
+ /* eslint-disable @babel/no-invalid-this */
13
+ async function loader(source) {
14
+ var _this$_compiler;
15
+
16
+ this.cacheable();
17
+ const target = (_this$_compiler = this._compiler) === null || _this$_compiler === void 0 ? void 0 : _this$_compiler.options.target;
18
+
19
+ if (target === 'node') {
20
+ return source;
21
+ }
22
+
23
+ const options = this.getOptions();
24
+ const code = (0, _generateClient.generateClient)({
25
+ basedir: options.routesDir,
26
+ filename: this.resourcePath
27
+ });
28
+ return code;
29
+ }