This repository has been archived by the owner on Oct 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 220
/
webpack-plugin.ts
140 lines (117 loc) · 3.7 KB
/
webpack-plugin.ts
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import {join} from 'path';
import type {Compiler} from 'webpack';
import {WatchIgnorePlugin} from 'webpack';
import VirtualModulesPlugin from 'webpack-virtual-modules';
import type {Options} from './shared';
import {HEADER, Entrypoint, noSourceExists} from './shared';
import {errorSSRComponentExists, errorClientSource} from './error';
/**
* A webpack plugin that generates default server and client entrypoints if none are present.
* @param config
* @returns a customized webpack plugin
*/
export class ReactServerPlugin {
private options: Options;
constructor({
host,
port,
assetPrefix,
basePath = '.',
proxy = false,
}: Partial<Options> = {}) {
this.options = {
basePath,
host,
port,
assetPrefix,
proxy,
};
}
apply(compiler: Compiler) {
const modules = this.modules(compiler);
const virtualModules = new VirtualModulesPlugin(modules);
(virtualModules as any).apply(compiler);
const ignorePaths = (Object.keys(modules) as (string | RegExp)[]).concat(
compiler.options.watchOptions.ignored || [],
);
const watchIgnore = new WatchIgnorePlugin({paths: ignorePaths});
watchIgnore.apply(compiler);
}
private modules(compiler: Compiler) {
const {basePath} = this.options;
const modules: {[key: string]: string} = {};
if (noSourceExists(Entrypoint.Client, this.options, compiler)) {
const file = join(basePath, `${Entrypoint.Client}.js`);
modules[file] = clientSource();
}
if (noSourceExists(Entrypoint.Server, this.options, compiler)) {
const file = join(basePath, `${Entrypoint.Server}.js`);
modules[file] = serverSource(this.options, compiler);
}
if (errorSSRComponentExists(this.options, compiler)) {
const file = join(basePath, `${Entrypoint.Error}.entry.client.js`);
modules[file] = errorClientSource();
}
return modules;
}
}
function serverSource(options: Options, compiler: Compiler) {
const {port, host, assetPrefix, proxy} = options;
return `
${HEADER}
import React from 'react';
import {createServer} from '@shopify/react-server';
import App from 'index';
${
errorSSRComponentExists(options, compiler)
? "import Error from 'error';"
: ''
}
process.on('uncaughtException', logError);
process.on('unhandledRejection', logError);
function logError(error) {
const errorLog = \`\${error.stack || error.message || 'No stack trace was present'}\`;
console.log(\`React Server failed to start.\n\${errorLog}\`);
process.exit(1);
}
const render = (ctx) => {
return React.createElement(App, {
url: ctx.request.URL,
data: ctx.state.quiltData,
});
}
const app = createServer({
port: ${port},
ip: ${JSON.stringify(host)},
assetPrefix: ${JSON.stringify(assetPrefix)},
proxy: ${proxy},
render,
${
errorSSRComponentExists(options, compiler)
? `renderError: (ctx) => {
return React.createElement(Error, {
url: ctx.request.url,
data: ctx.state.quiltData,
error: ctx.state.quiltError,
});
}`
: ''
}
});
export default app;
`;
}
function clientSource() {
return `
${HEADER}
import React from 'react';
import ReactDOM from 'react-dom/client';
import {showPage, getSerialized} from '@shopify/react-html';
import App from 'index';
const appContainer = document.getElementById('app');
const data = getSerialized('quilt-data');
const url = new URL(window.location.href);
ReactDOM.hydrateRoot(appContainer, React.createElement(App, {data, url}));
showPage();
`;
}