flecks/packages/web/build/runtime.js
2024-01-20 03:58:07 -06:00

162 lines
4.9 KiB
JavaScript

const {access, readFile} = require('fs/promises');
const {
basename,
dirname,
extname,
join,
} = require('path');
const Server = require('@flecks/core/build/server');
const {glob} = require('@flecks/core/server');
module.exports = async (config, env, argv, flecks) => {
const buildFlecks = await Server.from({
config: flecks.realiasedConfig,
platforms: ['client', '!server'],
});
const {resolver, flecks: webFlecks} = buildFlecks;
const paths = Object.keys(webFlecks)
.filter((fleck) => !['@flecks/server'].includes(fleck));
const styles = (
await Promise.all(
Object.keys(flecks.flecks)
.map(async (fleck) => {
// No root? How to infer?
const [root] = Object.entries(flecks.roots)
.find(([root]) => fleck.startsWith(root)) || [];
if (!root) {
return undefined;
}
// Compiled? It will be included with the compilation.
if (
Object.entries(flecks.compiled)
.some(([, {flecks}]) => flecks.includes(fleck))
) {
return undefined;
}
try {
const sub = fleck.slice(root.length + 1) || 'index';
const style = join(root, 'assets', `${basename(sub, extname(sub))}.css`);
await access(await flecks.resolver.resolve(style));
return style;
}
catch (error) {
return undefined;
}
}),
)
)
.filter((filename) => !!filename);
const runtime = await flecks.resolver.resolve(join('@flecks/web/runtime'));
const isProduction = 'production' === argv.mode;
const resolvedPaths = (await Promise.all(
paths.map(async (path) => [path, await flecks.resolver.resolve(path)]),
))
.filter(([, resolved]) => resolved)
.map(([path]) => path);
const source = [
'module.exports = (update) => (async () => ({',
" config: window[Symbol.for('@flecks/web.config')],",
' flecks: Object.fromEntries(await Promise.all([',
...resolvedPaths
.map((path) => [
' [',
` '${path}',`,
` import('${path}').then((M) => (update(${paths.length}, '${path}'), M)),`,
' ],',
]).flat(),
' ].map(async ([path, M]) => [path, await M]))),',
" platforms: ['client'],",
'}))();',
'',
];
// HMrequire.
source.push('if (module.hot) {');
resolvedPaths.forEach((path) => {
source.push(` module.hot.accept('${path}', async () => {`);
source.push(` const updatedFleck = require('${path}');`);
source.push(` window.flecks.refresh('${path}', updatedFleck);`);
source.push(` window.flecks.invoke('@flecks/core.hmr', '${path}', updatedFleck);`);
source.push(' });');
});
source.push('}');
source.push('');
// Create runtime.
config.module.rules.push({
test: runtime,
use: [
{
loader: runtime,
options: {
source: source.join('\n'),
},
},
],
});
config.resolve.alias['@flecks/web/runtime$'] = runtime;
// Stubs.
buildFlecks.stubs.forEach((stub) => {
config.module.rules.push(
{
test: stub,
use: 'null-loader',
},
);
});
await buildFlecks.runtimeCompiler('web', config);
// Styles.
config.entry.index.push(...styles);
// Tests.
if (!isProduction) {
const testEntries = (await Promise.all(
Object.entries(buildFlecks.roots)
.map(async ([parent, {request}]) => {
const tests = [];
const resolved = dirname(await resolver.resolve(join(request, 'package.json')));
const rootTests = await glob(join(resolved, 'test', '*.js'));
tests.push(
...rootTests
.map((test) => test.replace(resolved, parent)),
);
const platformTests = await Promise.all(
buildFlecks.platforms.map((platform) => (
glob(join(resolved, 'test', 'platforms', platform, '*.js'))
)),
);
tests.push(
...platformTests
.flat()
.map((test) => test.replace(resolved, parent)),
);
return [parent, tests];
}),
))
.filter(([, tests]) => tests.length > 0);
const tests = await resolver.resolve(
join('@flecks/web', 'server', 'build', 'tests'),
);
const testsSource = (await readFile(tests)).toString();
config.module.rules.push({
test: tests,
use: [
{
loader: runtime,
options: {
source: testsSource.replace(
" await import('@flecks/web/tests');",
testEntries
.map(([root, tests]) => (
[
` describe('${root}', () => {`,
` ${tests.map((test) => `require('${test}');`).join('\n ')}`,
' });',
].join('\n')
)).join('\n\n'),
),
},
},
],
});
}
};