Skip to content
This repository was archived by the owner on May 19, 2025. It is now read-only.

Commit fccdb4d

Browse files
authored
fix: remove incorrect module path resolution, set correct rollup bundle input (#129)
* fix: remove incorrect module path resoltion, set correct rollup bundle input * refactor: start moving scripts into manifest.json * refactor: rework development bundler endpoints * chore: add missing file * refactor: use strategy and scope, move assets into the head * refactor: update deps, fix tests, move scripts back into manifest file * refactor: cleanup
1 parent 7fd8914 commit fccdb4d

20 files changed

+486
-418
lines changed

api/build.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,22 @@ export async function build({ state, config, cwd = process.cwd() }) {
5959
const LAZY_INTERMEDIATE = join(ESBUILD_OUTDIR, 'lazy.js');
6060
const LAZY_FINAL = join(CLIENT_OUTDIR, 'lazy.js');
6161

62+
const hydrateSupport =
63+
MODE === 'hydrate'
64+
? 'import "@lit-labs/ssr-client/lit-element-hydrate-support.js";'
65+
: '';
66+
6267
// Create entrypoints for each file type
6368
if (existsSync(CONTENT_SRC_FILEPATH)) {
6469
writeFileSync(
6570
CONTENT_ENTRY,
66-
`import "${require.resolve(
67-
'@lit-labs/ssr-client/lit-element-hydrate-support.js',
68-
)}";import Component from "${CONTENT_SRC_FILEPATH}";customElements.define("${NAME}-content",Component);`,
71+
`${hydrateSupport}import Component from "${CONTENT_SRC_FILEPATH}";customElements.define("${NAME}-content",Component);`,
6972
);
7073
}
7174
if (existsSync(FALLBACK_SRC_FILEPATH)) {
7275
writeFileSync(
7376
FALLBACK_ENTRY,
74-
`import "${require.resolve(
75-
'@lit-labs/ssr-client/lit-element-hydrate-support.js',
76-
)}";import Component from "${FALLBACK_SRC_FILEPATH}";customElements.define("${NAME}-fallback",Component);`,
77+
`${hydrateSupport}import Component from "${FALLBACK_SRC_FILEPATH}";customElements.define("${NAME}-fallback",Component);`,
7778
);
7879
}
7980

@@ -151,6 +152,7 @@ export async function build({ state, config, cwd = process.cwd() }) {
151152
filter: /(content|fallback|lazy|scripts|src|server).*.(ts|js)$/,
152153
namespace: 'file',
153154
},
155+
// @ts-ignore
154156
async (args) => {
155157
if (
156158
args.path.includes('node_modules') ||
@@ -202,7 +204,7 @@ export async function build({ state, config, cwd = process.cwd() }) {
202204
async function buildRollupConfig(options) {
203205
const rollupConfig = [];
204206
for (const filepath of options) {
205-
const input = filepath.replace('-entrypoint', '');
207+
const input = filepath;
206208

207209
let outfile;
208210
if (filepath === CONTENT_ENTRY) {

lib/plugin.js

Lines changed: 34 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@ import assetsPn from '../plugins/assets.js';
99
import compressionPn from '../plugins/compression.js';
1010
import errorsPn from '../plugins/errors.js';
1111
import exceptionsPn from '../plugins/exceptions.js';
12-
import hydratePn from '../plugins/hydrate.js';
1312
import importElementPn from '../plugins/import-element.js';
1413
import liveReloadPn from '../plugins/live-reload.js';
1514
import localePn from '../plugins/locale.js';
1615
import metricsPn from '../plugins/metrics.js';
1716
import podletPn from '../plugins/podlet.js';
18-
import scriptPn from '../plugins/script.js';
1917
import timingPn from '../plugins/timing.js';
2018
import validationPn from '../plugins/validation.js';
2119
import lazyPn from '../plugins/lazy.js';
2220
import scriptsPn from '../plugins/scripts.js';
23-
import ssrPn from '../plugins/ssr.js';
24-
import csrPn from '../plugins/csr.js';
2521
import documentPn from '../plugins/document.js';
2622
import docsPn from '../plugins/docs.js';
2723
import bundlerPn from '../plugins/bundler.js';
24+
import modeSupportPn from '../plugins/mode-support.js';
25+
import hydrateSupportPn from '../plugins/hydrate-support.js';
2826
import { isAbsoluteURL, joinURLPathSegments } from './utils.js';
2927

3028
const defaults = {
@@ -35,7 +33,7 @@ const defaults = {
3533

3634
/**
3735
* create an intersection type out of fastify instance and its decorated properties
38-
* @typedef {import("fastify").FastifyInstance & { podlet: any, metrics: any, schemas: any, importElement: function, readTranslations: function, script: function, hydrate: function, ssr: function, csr: function }} FastifyInstance
36+
* @typedef {import("fastify").FastifyInstance & { podlet: any, metrics: any, schemas: any, importElement: function, readTranslations: function, script: function, serverRender: function, clientRender: function }} FastifyInstance
3937
*/
4038

4139
/**
@@ -47,7 +45,7 @@ export default fp(
4745
/**
4846
*
4947
* @param {import("fastify").FastifyInstance} fastify
50-
* @param {{ prefix: string, extensions: import("./resolvers/extensions.js").Extensions, cwd: string, plugins: import("esbuild").Plugin[], config: import("convict").Config, webSocketServer?: import("ws").WebSocketServer, clientWatcher?: import("chokidar").FSWatcher }} options
48+
* @param {{ prefix?: string, extensions?: import("./resolvers/extensions.js").Extensions, cwd?: string, plugins?: import("esbuild").Plugin[], config: import("convict").Config, webSocketServer?: import("ws").WebSocketServer, clientWatcher?: import("chokidar").FSWatcher }} options
5149
*/
5250
async (
5351
fastify,
@@ -112,6 +110,12 @@ export default fp(
112110
fallback,
113111
development,
114112
});
113+
await f.register(hydrateSupportPn, {
114+
enabled: mode === 'hydrate',
115+
base,
116+
development,
117+
prefix,
118+
});
115119
await f.register(lazyPn, { enabled: lazy, base, development, prefix });
116120
await f.register(scriptsPn, {
117121
enabled: scripts,
@@ -132,19 +136,12 @@ export default fp(
132136
await f.register(assetsPn, { base, cwd });
133137
await f.register(bundlerPn, { cwd, development, plugins });
134138
await f.register(exceptionsPn, { grace, development });
135-
await f.register(hydratePn, {
136-
appName: name,
137-
base: assetBase,
138-
development,
139-
prefix,
140-
});
141-
await f.register(csrPn, {
139+
await f.register(modeSupportPn, {
142140
appName: name,
143141
base: assetBase,
144142
development,
145143
prefix,
146144
});
147-
await f.register(ssrPn, { appName: name, base, prefix, development });
148145
await f.register(importElementPn, {
149146
appName: name,
150147
development,
@@ -153,7 +150,6 @@ export default fp(
153150
});
154151
await f.register(localePn, { locale, cwd });
155152
await f.register(metricsPn);
156-
await f.register(scriptPn, { development });
157153
await f.register(timingPn, {
158154
timeAllRoutes,
159155
groupStatusCodes,
@@ -175,58 +171,34 @@ export default fp(
175171
// routes
176172
if (existsSync(contentFilePath)) {
177173
const tag = unsafeStatic(`${name}-content`);
174+
175+
// mount content route
178176
f.get(f.podlet.content(), async (request, reply) => {
179177
try {
180178
const contextConfig = /** @type {FastifyContextConfig} */ (
181179
reply.context.config
182180
);
183181
contextConfig.timing = true;
184182

185-
if (mode === 'ssr-only' || mode === 'hydrate') {
186-
// import server side component
187-
await f.importElement(contentFilePath);
188-
}
189-
183+
// use developer provided content state function to generate initial content state
190184
const initialState = JSON.stringify(
191185
// @ts-ignore
192186
(await contentStateFn(request, reply.app.podium.context)) || '',
193187
);
194188

195189
const messages = await f.readTranslations();
196-
197190
const translations = messages ? JSON.stringify(messages) : '';
198191

199192
// includes ${null} hack for SSR. See https://github.com/lit/lit/issues/2246
200-
const template = html`
201-
<${tag} version="${version}" locale='${locale}' translations='${translations}'
202-
initial-state='${initialState}'></${tag}>${null} `;
203-
const hydrateSupport =
204-
mode === 'hydrate'
205-
? f.script(
206-
joinURLPathSegments(
207-
prefix,
208-
'/_/dynamic/modules/@lit-labs/ssr-client/lit-element-hydrate-support.js',
209-
),
210-
{ dev: true },
211-
)
212-
: '';
213-
let markup;
214-
if (mode === 'ssr-only') {
215-
markup = f.ssr('content', template);
216-
} else if (mode === 'csr-only') {
217-
markup = f.csr(
218-
'content',
219-
`<${name}-content version="${version}" locale='${locale}' translations='${translations}' initial-state='${initialState}'></${name}-content>`,
220-
);
221-
} else {
222-
markup = f.hydrate('content', template);
223-
}
193+
const template = html`<${tag} version="${version}" locale='${locale}' translations='${translations}' initial-state='${initialState}'></${tag}>${null} `;
224194

225-
reply
226-
.type('text/html; charset=utf-8')
227-
.send(`${hydrateSupport}${markup}`);
195+
reply.type('text/html; charset=utf-8');
228196

229-
return reply;
197+
if (mode === 'csr-only')
198+
return reply.send(f.clientRender('content', template));
199+
// import the custom element for server side use
200+
await f.importElement(contentFilePath);
201+
return reply.send(f.serverRender('content', template));
230202
} catch (err) {
231203
f.log.error(err);
232204
return reply;
@@ -236,56 +208,35 @@ export default fp(
236208

237209
if (existsSync(fallbackFilePath)) {
238210
const tag = unsafeStatic(`${name}-fallback`);
211+
212+
// mount fallback route
239213
f.get(f.podlet.fallback(), async (request, reply) => {
240214
try {
241215
const contextConfig = /** @type {FastifyContextConfig} */ (
242216
reply.context.config
243217
);
244218
contextConfig.timing = true;
245219

246-
if (mode === 'ssr-only' || mode === 'hydrate') {
247-
// import server side component
248-
await f.importElement(fallbackFilePath);
249-
}
250-
220+
// use developer provided fallback state function to generate initial fallback state
251221
const initialState = JSON.stringify(
252222
// @ts-ignore
253223
(await fallbackStateFn(request, reply.app.podium.context)) ||
254224
'',
255225
);
256226

257227
const messages = await f.readTranslations();
258-
259228
const translations = messages ? JSON.stringify(messages) : '';
260-
const template = html`
261-
<${tag} version="${version}" locale='${locale}' translations='${translations}'
262-
initial-state='${initialState}'></${tag}>${null} `;
263-
const hydrateSupport =
264-
mode === 'hydrate'
265-
? f.script(
266-
joinURLPathSegments(
267-
prefix,
268-
'/_/dynamic/modules/@lit-labs/ssr-client/lit-element-hydrate-support.js',
269-
),
270-
{ dev: true },
271-
)
272-
: '';
273-
let markup;
274-
if (mode !== 'ssr-only') {
275-
markup = f.ssr('fallback', template);
276-
} else if (mode === 'csr-only') {
277-
markup = f.csr(
278-
'fallback',
279-
`<${name}-fallback version="${version}" locale='${locale}' translations='${translations}' initial-state='${initialState}'></${name}-fallback>`,
280-
);
281-
} else {
282-
markup = f.hydrate('fallback', template);
283-
}
284-
reply
285-
.type('text/html; charset=utf-8')
286-
.send(`${hydrateSupport}${markup}`);
287229

288-
return reply;
230+
// includes ${null} hack for SSR. See https://github.com/lit/lit/issues/2246
231+
const template = html`<${tag} version="${version}" locale='${locale}' translations='${translations}' initial-state='${initialState}'></${tag}>${null} `;
232+
233+
reply.type('text/html; charset=utf-8');
234+
235+
if (mode === 'csr-only')
236+
return reply.send(f.clientRender('fallback', template));
237+
// import the custom element for server side use
238+
await f.importElement(fallbackFilePath);
239+
return reply.send(f.serverRender('fallback', template));
289240
} catch (err) {
290241
f.log.error(err);
291242
return reply;

package.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
"scripts": {
2121
"lint": "eslint .",
2222
"lint:fix": "eslint --fix .",
23-
"test": "NODE_OPTIONS=--conditions=development tap --timeout 60000 --no-check-coverage --no-coverage-report test/**/*.test.js",
24-
"prepublish": "tsc"
23+
"test": "NODE_OPTIONS=--conditions=development tap --timeout 60000 --no-check-coverage --no-coverage-report test/**/*.test.js"
2524
},
2625
"keywords": [],
2726
"author": "",
@@ -39,11 +38,11 @@
3938
"@fastify/restartable": "2.1.0",
4039
"@fastify/static": "6.10.2",
4140
"@lingui/cli": "4.3.0",
42-
"@lit-labs/ssr": "3.1.4",
43-
"@lit-labs/ssr-client": "1.1.2",
41+
"@lit-labs/ssr": "3.2.0",
42+
"@lit-labs/ssr-client": "1.1.5",
4443
"@metrics/client": "2.5.0",
4544
"@podium/fastify-podlet": "3.0.0-next.3",
46-
"@podium/podlet": "5.0.0-next.6",
45+
"@podium/podlet": "5.0.0-next.8",
4746
"@rollup/plugin-commonjs": "25.0.2",
4847
"@rollup/plugin-node-resolve": "15.1.0",
4948
"@rollup/plugin-replace": "5.0.4",
@@ -64,7 +63,7 @@
6463
"fastify-plugin": "4.5.0",
6564
"http-errors": "2.0.0",
6665
"kill-port": "2.0.1",
67-
"lit": "2.7.5",
66+
"lit": "3.1.0",
6867
"lodash.merge": "4.6.2",
6968
"minify-html-literals": "1.3.5",
7069
"ora": "7.0.1",

plugins/bundler.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,35 @@ export default fp(
3737

3838
await fastify.register(etag, { algorithm: 'fnv1a' });
3939

40+
/**
41+
* Special case route.
42+
* Lit SSR client supports Node and browser environments. When we try to use require.resolve we get the Node version
43+
* when we want the browser version so we trick it by running a build on file containing only an import statement
44+
*/
45+
fastify.get(
46+
'/_/dynamic/modules/@lit-labs/ssr-client/lit-element-hydrate-support.js',
47+
async (/** @type {Request} */ request, reply) => {
48+
const filepath = join(new URL("..", import.meta.url).pathname, "lib", "lit-element-hydrate-support.js");
49+
50+
try {
51+
const body = await build({ entryPoints: [filepath], plugins });
52+
53+
reply.type('application/javascript');
54+
reply.send(body);
55+
return reply;
56+
} catch (err) {
57+
fastify.log.error(err)
58+
throw new httpError.NotFound();
59+
}
60+
},
61+
);
62+
63+
/**
64+
* Endpoint to dynamically bundle and serve Node modules for the frontend when in dev mode
65+
* This endpoint is not used in production as dependencies will be included in a production build when running "podlet build"
66+
*
67+
* "*" is a Node package name including scope. Eg. lit or @lit-labs/ssr
68+
*/
4069
fastify.get(
4170
'/_/dynamic/modules/*',
4271
async (/** @type {Request} */ request, reply) => {
@@ -60,6 +89,35 @@ export default fp(
6089
},
6190
);
6291

92+
/**
93+
* Endpoint that wraps content.js or fallback.js definitions in a customElement.define call for use during development.
94+
* This endpoint is not used in production as content and fallback elements will be included in a production build when running "podlet build".
95+
*
96+
* :type is either "content" or "fallback"
97+
* :name is the application name
98+
*/
99+
fastify.get(
100+
'/_/dynamic/element/:type/:name',
101+
async (/** @type {Request} */ request, reply) => {
102+
// @ts-ignore
103+
const { type, name } = request.params;
104+
105+
reply
106+
.type('application/javascript')
107+
.send(
108+
`import El from '/_/dynamic/files/${type}.js';customElements.define("${name}-${type}",El);`,
109+
);
110+
return reply;
111+
},
112+
);
113+
114+
/**
115+
* Endpoint that accepts the name/path for a core project JavaScript file such as content.js or fallback.js
116+
* which it then bundles and serves.
117+
* This endpoint is not used in production as files will be included in a production build when running "podlet build"
118+
*
119+
* :file.js is either content.js, fallback.js, lazy.js or scripts.js
120+
*/
63121
fastify.get(
64122
'/_/dynamic/files/:file.js',
65123
async (/** @type {Request} */ request, reply) => {

0 commit comments

Comments
 (0)