Skip to content

Commit 1039efb

Browse files
committed
refactor: Swapping out async-loader for preact-iso's lazy
1 parent 4953687 commit 1039efb

File tree

10 files changed

+93
-67
lines changed

10 files changed

+93
-67
lines changed

jsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "ESNext",
4+
"module": "ESNext",
45
"moduleResolution": "Node",
56
"allowJs": true,
67
"checkJs": true,

packages/cli/lib/lib/babel-config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module.exports = function (env) {
2525
],
2626
].filter(Boolean),
2727
plugins: [
28+
require.resolve('@rschristian/babel-plugin-webpack-chunk-name-comments'),
2829
[require.resolve('@babel/plugin-proposal-decorators'), { legacy: true }],
2930
isProd &&
3031
require.resolve('babel-plugin-transform-react-remove-prop-types'),
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const { h, options, cloneElement } = require('preact');
2+
const renderToString = require('preact-render-to-string');
3+
4+
let vnodeHook;
5+
6+
const old = options.vnode;
7+
options.vnode = vnode => {
8+
if (old) old(vnode);
9+
if (vnodeHook) vnodeHook(vnode);
10+
};
11+
12+
/**
13+
* @param {ReturnType<h>} vnode The root JSX element to render (eg: `<App />`)
14+
* @param {object} [options]
15+
* @param {number} [options.maxDepth = 10] The maximum number of nested asynchronous operations to wait for before flushing
16+
* @param {object} [options.props] Additional props to merge into the root JSX element
17+
*/
18+
module.exports = async function prerender(vnode, options) {
19+
options = options || {};
20+
21+
const maxDepth = options.maxDepth || 10;
22+
const props = options.props;
23+
let tries = 0;
24+
25+
if (typeof vnode === 'function') {
26+
vnode = h(vnode, props);
27+
} else if (props) {
28+
vnode = cloneElement(vnode, props);
29+
}
30+
31+
const render = () => {
32+
if (++tries > maxDepth) return;
33+
try {
34+
return renderToString(vnode);
35+
} catch (e) {
36+
if (e && e.then) return e.then(render);
37+
throw e;
38+
}
39+
};
40+
41+
let links = new Set();
42+
vnodeHook = ({ type, props }) => {
43+
if (
44+
type === 'a' &&
45+
props &&
46+
props.href &&
47+
(!props.target || props.target === '_self')
48+
) {
49+
links.add(props.href);
50+
}
51+
};
52+
53+
try {
54+
let html = await render();
55+
html += `<script type="isodata"></script>`;
56+
return { html, links };
57+
} finally {
58+
vnodeHook = null;
59+
}
60+
};

packages/cli/lib/lib/webpack/prerender.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ const { red, yellow } = require('kleur');
22
const { resolve } = require('path');
33
const { readFileSync } = require('fs');
44
const stackTrace = require('stack-trace');
5-
const URL = require('url');
5+
const { URL } = require('url');
66
const { SourceMapConsumer } = require('source-map');
7+
const prerender = require('./preact-iso-prerender');
78

8-
module.exports = function (env, params) {
9+
module.exports = async function (env, params) {
910
params = params || {};
1011

1112
let entry = resolve(env.dest, './ssr-build/ssr-bundle.js');
1213
let url = params.url || '/';
1314

1415
global.history = {};
15-
global.location = { ...URL.parse(url) };
16+
global.location = new URL(url, 'http://localhost');
1617

1718
try {
1819
let m = require(entry),
@@ -25,12 +26,7 @@ module.exports = function (env, params) {
2526
);
2627
return '';
2728
}
28-
const { cwd } = env;
29-
const preact = require(require.resolve('preact', { paths: [cwd] }));
30-
const renderToString = require(require.resolve('preact-render-to-string', {
31-
paths: [cwd],
32-
}));
33-
return renderToString(preact.h(app, { ...params, url }));
29+
return (await prerender(app, { ...params, url })).html;
3430
} catch (err) {
3531
let stack = stackTrace.parse(err).filter(s => s.getFileName() === entry)[0];
3632
if (!stack) {

packages/cli/lib/lib/webpack/render-html-plugin.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ module.exports = async function renderHTMLPlugin(config) {
7272
title,
7373
filename,
7474
template: `!!${require.resolve('ejs-loader')}?esModule=false!${template}`,
75-
templateParameters: (compilation, assets, assetTags, options) => {
75+
templateParameters: async (compilation, assets, assetTags, options) => {
7676
let entrypoints = {};
7777
compilation.entrypoints.forEach((entrypoint, name) => {
7878
let entryFiles = entrypoint.getFiles();
@@ -91,7 +91,9 @@ module.exports = async function renderHTMLPlugin(config) {
9191
config,
9292
preRenderData: values,
9393
CLI_DATA: { preRenderData: { url, ...routeData } },
94-
ssr: config.prerender ? prerender({ cwd, dest, src }, values) : '',
94+
ssr: config.prerender
95+
? await prerender({ cwd, dest, src }, values)
96+
: '',
9597
entrypoints,
9698
},
9799
htmlWebpackPlugin: {

packages/cli/lib/lib/webpack/webpack-base-config.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,6 @@ module.exports = function createBaseConfig(env) {
127127
'react-dom': require.resolve('preact/compat'),
128128
'react/jsx-runtime': require.resolve('preact/jsx-runtime'),
129129
'react-addons-css-transition-group': 'preact-css-transition-group',
130-
'preact-cli/async-component': require.resolve(
131-
'@preact/async-loader/async'
132-
),
133130
},
134131
},
135132

@@ -321,7 +318,6 @@ module.exports = function createBaseConfig(env) {
321318
},
322319
},
323320
}),
324-
isProd && new webpack.LoaderOptionsPlugin({ minimize: true }),
325321
new webpack.optimize.ModuleConcatenationPlugin(),
326322

327323
// strip out babel-helper invariant checks

packages/cli/lib/lib/webpack/webpack-client-config.js

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ const webpack = require('webpack');
22
const { resolve, join } = require('path');
33
const { existsSync } = require('fs');
44
const { merge } = require('webpack-merge');
5-
const { filter } = require('minimatch');
65
const SizePlugin = require('size-plugin');
76
const CopyWebpackPlugin = require('copy-webpack-plugin');
87
const TerserPlugin = require('terser-webpack-plugin');
@@ -14,21 +13,14 @@ const baseConfig = require('./webpack-base-config');
1413
const { InjectManifest } = require('workbox-webpack-plugin');
1514
const CompressionPlugin = require('compression-webpack-plugin');
1615
const RefreshPlugin = require('@prefresh/webpack');
17-
const { normalizePath, warn } = require('../../util');
16+
const { warn } = require('../../util');
1817
const OptimizePlugin = require('optimize-plugin');
1918

20-
const cleanFilename = name =>
21-
name.replace(
22-
/(^\/(routes|components\/(routes|async))\/|(\/index)?\.[jt]sx?$)/g,
23-
''
24-
);
25-
2619
/**
2720
* @returns {Promise<import('webpack').Configuration>}
2821
*/
2922
async function clientConfig(env) {
3023
const { source, src } = env;
31-
const asyncLoader = require.resolve('@preact/async-loader');
3224

3325
let entry = {
3426
bundle: resolve(__dirname, './../entry'),
@@ -87,43 +79,9 @@ async function clientConfig(env) {
8779
}
8880
return env.isProd ? '[name].[chunkhash:5].js' : '[name].js';
8981
},
90-
chunkFilename: '[name].chunk.[chunkhash:5].js',
91-
},
92-
93-
resolveLoader: {
94-
alias: {
95-
async: asyncLoader,
96-
},
97-
},
98-
99-
// automatic async components :)
100-
module: {
101-
rules: [
102-
{
103-
test: /\.[jt]sx?$/,
104-
include: [
105-
filter(source('routes') + '/{*,*/index}.{js,jsx,ts,tsx}'),
106-
filter(
107-
source('components') +
108-
'/{routes,async}/{*,*/index}.{js,jsx,ts,tsx}'
109-
),
110-
],
111-
loader: asyncLoader,
112-
options: {
113-
name(filename) {
114-
filename = normalizePath(filename);
115-
let relative = filename.replace(normalizePath(src), '');
116-
if (!relative.includes('/routes/')) return false;
117-
return 'route-' + cleanFilename(relative);
118-
},
119-
formatName(filename) {
120-
filename = normalizePath(filename);
121-
let relative = filename.replace(normalizePath(source('.')), '');
122-
return cleanFilename(relative);
123-
},
124-
},
125-
},
126-
],
82+
chunkFilename: env.isProd
83+
? '[name].chunk.[chunkhash:5].js'
84+
: '[name].chunk.js',
12785
},
12886

12987
plugins: [

packages/cli/lib/lib/webpack/webpack-server-config.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const webpack = require('webpack');
12
const { resolve } = require('path');
23
const { merge } = require('webpack-merge');
34
const baseConfig = require('./webpack-base-config');
@@ -20,11 +21,11 @@ function serverConfig(env) {
2021
preact: 'preact',
2122
},
2223
target: 'node',
23-
resolveLoader: {
24-
alias: {
25-
async: resolve(__dirname, './dummy-loader'),
26-
},
27-
},
24+
plugins: [
25+
new webpack.optimize.LimitChunkCountPlugin({
26+
maxChunks: 1,
27+
}),
28+
],
2829
};
2930
}
3031

packages/cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"p-retry": "^4.5.0",
4343
"polka": "^0.5.2",
4444
"preact": "^10.6.5",
45+
"preact-iso": "^2.3.0",
4546
"preact-render-to-string": "^5.1.19",
4647
"preact-router": "^3.0.1",
4748
"puppeteer": "^9.1.1",
@@ -77,9 +78,9 @@
7778
"@babel/plugin-transform-react-jsx": "^7.13.12",
7879
"@babel/preset-env": "^7.13.15",
7980
"@babel/preset-typescript": "^7.13.0",
80-
"@preact/async-loader": "^3.0.1",
8181
"@prefresh/babel-plugin": "^0.4.1",
8282
"@prefresh/webpack": "^3.2.2",
83+
"@rschristian/babel-plugin-webpack-chunk-name-comments": "^0.1.2",
8384
"@types/webpack": "^4.38.0",
8485
"autoprefixer": "^10.4.7",
8586
"babel-loader": "^8.2.5",

yarn.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2640,6 +2640,11 @@
26402640
estree-walker "^1.0.1"
26412641
picomatch "^2.2.2"
26422642

2643+
"@rschristian/babel-plugin-webpack-chunk-name-comments@^0.1.2":
2644+
version "0.1.2"
2645+
resolved "https://registry.yarnpkg.com/@rschristian/babel-plugin-webpack-chunk-name-comments/-/babel-plugin-webpack-chunk-name-comments-0.1.2.tgz#5c2101a7241ce47051493b691090412146cb592e"
2646+
integrity sha512-2kzxyBZB8LhUUPzs1+4yXloLmS074aBdlv5hf7/ntodMfCFoHFyfS+zLYXYek8E3pANQGN29eNHWQZABehe8ig==
2647+
26432648
"@sindresorhus/is@^0.14.0":
26442649
version "0.14.0"
26452650
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@@ -11386,6 +11391,11 @@ postcss@^8.4.13:
1138611391
picocolors "^1.0.0"
1138711392
source-map-js "^1.0.2"
1138811393

11394+
preact-iso@^2.3.0:
11395+
version "2.3.0"
11396+
resolved "https://registry.yarnpkg.com/preact-iso/-/preact-iso-2.3.0.tgz#ab6c5de28df9e0f7a0589dd2175a83ba821f69ec"
11397+
integrity sha512-taJmRidbWrjHEhoVoxXS2Kvxa6X3jXSsTtD7rSYeJuxnPNr1ghCu1JUzCrRxmZwTUNWIqwUpNi+AJoLtvCPN7g==
11398+
1138911399
preact-render-to-string@^5.1.19:
1139011400
version "5.1.19"
1139111401
resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.1.19.tgz#ffae7c3bd1680be5ecf5991d41fe3023b3051e0e"

0 commit comments

Comments
 (0)