forked from preactjs/preact-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
prerender.js
150 lines (136 loc) · 4.36 KB
/
prerender.js
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
141
142
143
144
145
146
147
148
149
150
const { red, yellow } = require('kleur');
const { resolve } = require('path');
const { readFileSync } = require('fs');
const stackTrace = require('stack-trace');
const URL = require('url');
const { SourceMapConsumer } = require('source-map');
module.exports = function (env, params) {
params = params || {};
let entry = resolve(env.dest, './ssr-build/ssr-bundle.js');
let url = params.url || '/';
global.history = {};
global.location = { ...URL.parse(url) };
try {
let m = require(entry),
app = (m && m.default) || m;
if (typeof app !== 'function') {
// eslint-disable-next-line no-console
console.warn(
'Entry does not export a Component function/class, aborting prerendering.'
);
return '';
}
const { cwd } = env;
const preact = require(require.resolve('preact', { paths: [cwd] }));
const renderToString = require(require.resolve('preact-render-to-string', {
paths: [cwd],
}));
return renderToString(preact.h(app, { ...params, url }));
} catch (err) {
let stack = stackTrace.parse(err).filter(s => s.getFileName() === entry)[0];
if (!stack) {
throw err;
}
handlePrerenderError(err, env, stack, entry);
}
};
async function handlePrerenderError(err, env, stack, entry) {
let errorMessage = err.toString();
let isReferenceError = errorMessage.startsWith('ReferenceError');
let methodName = stack.getMethodName();
let sourceMapContent, position, sourcePath, sourceLines, sourceCodeHighlight;
try {
sourceMapContent = JSON.parse(readFileSync(`${entry}.map`, 'utf-8'));
} catch (err) {
process.stderr.write(red(`Unable to read sourcemap: ${entry}.map\n`));
}
if (sourceMapContent) {
await SourceMapConsumer.with(sourceMapContent, null, consumer => {
position = consumer.originalPositionFor({
line: stack.getLineNumber(),
column: stack.getColumnNumber(),
});
});
if (position.source) {
position.source = position.source
.replace('webpack://', '.')
.replace(/^.*~\/((?:@[^/]+\/)?[^/]+)/, (s, name) =>
require
.resolve(name)
.replace(/^(.*?\/node_modules\/(@[^/]+\/)?[^/]+)(\/.*)$/, '$1')
);
sourcePath = resolve(env.src, position.source);
sourceLines;
try {
sourceLines = readFileSync(sourcePath, 'utf-8').split('\n');
} catch (err) {
try {
sourceLines = readFileSync(
require.resolve(position.source),
'utf-8'
).split('\n');
} catch (err) {
process.stderr.write(red(`Unable to read file: ${sourcePath}\n`));
}
// process.stderr.write(red(`Unable to read file: ${sourcePath}\n`));
}
sourceCodeHighlight = '';
}
if (sourceLines) {
for (var i = -4; i <= 4; i++) {
let color = i === 0 ? red : yellow;
let line = position.line + i;
let sourceLine = sourceLines[line - 1];
sourceCodeHighlight += sourceLine ? `${color(sourceLine)}\n` : '';
}
}
}
process.stderr.write('\n');
process.stderr.write(red(`${errorMessage}\n`));
// check if we have methodName (ie, the error originated in user code)
if (methodName) {
process.stderr.write(`method: ${methodName}\n`);
if (sourceMapContent & sourceCodeHighlight) {
process.stderr.write(
`at: ${sourcePath}:${position.line}:${position.column}\n`
);
process.stderr.write('\n');
process.stderr.write('Source code:\n\n');
process.stderr.write(sourceCodeHighlight);
process.stderr.write('\n');
} else {
process.stderr.write('\n');
process.stderr.write('Stack:\n\n');
process.stderr.write(JSON.stringify(stack, null, 4) + '\n');
}
} else {
process.stderr.write(
yellow(
'Cannot determine error position. This most likely means it originated in node_modules.'
)
);
process.stderr.write('\n\n');
}
process.stderr.write(
`This ${
isReferenceError ? 'is most likely' : 'could be'
} caused by using DOM or Web APIs.\n`
);
process.stderr.write(
'Pre-render runs in node and has no access to globals available in browsers.\n\n'
);
process.stderr.write(
'Consider wrapping code producing error in: "if (typeof window !== "undefined") { ... }"\n'
);
if (methodName === 'componentWillMount') {
process.stderr.write('or place logic in "componentDidMount" method.\n');
}
process.stderr.write('\n');
process.stderr.write(
'Alternatively use "preact build --no-prerender" to disable prerendering.\n\n'
);
process.stderr.write(
'See https://github.com/preactjs/preact-cli#pre-rendering for further information.\n\n'
);
process.exit(1);
}