refactor: build

This commit is contained in:
cha0s 2022-03-12 07:01:00 -06:00
parent 668304408d
commit 9f08899cb5
29 changed files with 394 additions and 277 deletions

View File

@ -4,19 +4,20 @@ const {join} = require('path');
const airbnb = require('@neutrinojs/airbnb'); const airbnb = require('@neutrinojs/airbnb');
const banner = require('@neutrinojs/banner'); const banner = require('@neutrinojs/banner');
const copy = require('@neutrinojs/copy'); const copy = require('@neutrinojs/copy');
const node = require('@neutrinojs/node');
const glob = require('glob'); const glob = require('glob');
const fleck = require('../src/bootstrap/fleck');
const { const {
FLECKS_CORE_ROOT = process.cwd(), FLECKS_CORE_ROOT = process.cwd(),
} = process.env; } = process.env;
module.exports = require('../src/bootstrap/fleck.neutrinorc'); const config = require('../src/bootstrap/fleck.neutrinorc');
// Dotfiles. // Dotfiles.
module.exports.use.push((neutrino) => { config.use.push(({config}) => {
['eslintrc', 'eslint.defaults'].forEach((filename) => { ['eslintrc', 'eslint.defaults'].forEach((filename) => {
neutrino.config config
.entry(`server/build/.${filename}`) .entry(`server/build/.${filename}`)
.clear() .clear()
.add(`./src/server/build/${filename}`); .add(`./src/server/build/${filename}`);
@ -24,23 +25,19 @@ module.exports.use.push((neutrino) => {
}); });
// Tests. // Tests.
module.exports.use.push((neutrino) => { config.use.push(({config}) => {
// Test entrypoint. // Test entrypoint.
const testPaths = glob.sync(join(FLECKS_CORE_ROOT, 'test/*.js')); const testPaths = glob.sync(join(FLECKS_CORE_ROOT, 'test/*.js'));
testPaths.push(...glob.sync(join(FLECKS_CORE_ROOT, `test/platforms/server/*.js`))); testPaths.push(...glob.sync(join(FLECKS_CORE_ROOT, `test/platforms/server/*.js`)));
if (testPaths.length > 0) { if (testPaths.length > 0) {
const testEntry = neutrino.config.entry('test').clear(); const testEntry = config.entry('test').clear();
testPaths.forEach((path) => testEntry.add(path)); testPaths.forEach((path) => testEntry.add(path));
} }
}); });
module.exports.use.unshift((neutrino) => { config.use.unshift(fleck());
neutrino.config.plugins.delete('start-server');
});
module.exports.use.unshift(node({clean: {cleanStaleWebpackAssets: false}})); config.use.unshift(
module.exports.use.unshift(
airbnb({ airbnb({
eslint: { eslint: {
baseConfig: { baseConfig: {
@ -53,14 +50,14 @@ module.exports.use.unshift(
}), }),
); );
module.exports.use.push(banner({ config.use.push(banner({
banner: '#!/usr/bin/env node', banner: '#!/usr/bin/env node',
include: /^cli\.js$/, include: /^cli\.js$/,
pluginId: 'shebang', pluginId: 'shebang',
raw: true, raw: true,
})) }))
module.exports.use.push(({config}) => { config.use.push(({config}) => {
config config
.plugin('executable') .plugin('executable')
.use(class Executable { .use(class Executable {
@ -76,3 +73,5 @@ module.exports.use.push(({config}) => {
}); });
}); });
module.exports = config;

View File

@ -54,9 +54,10 @@
"@babel/preset-env": "^7.12.11", "@babel/preset-env": "^7.12.11",
"@babel/register": "^7.12.10", "@babel/register": "^7.12.10",
"@neutrinojs/airbnb": "^9.4.0", "@neutrinojs/airbnb": "^9.4.0",
"@neutrinojs/banner": "^9.4.0",
"@neutrinojs/clean": "^9.5.0",
"@neutrinojs/compile-loader": "^9.5.0", "@neutrinojs/compile-loader": "^9.5.0",
"@neutrinojs/copy": "^9.4.0", "@neutrinojs/copy": "^9.4.0",
"@neutrinojs/node": "^9.1.0",
"babel-merge": "^3.0.0", "babel-merge": "^3.0.0",
"babel-plugin-prepend": "^1.0.2", "babel-plugin-prepend": "^1.0.2",
"chai": "4.2.0", "chai": "4.2.0",
@ -80,7 +81,6 @@
"webpack-node-externals": "2.5.2" "webpack-node-externals": "2.5.2"
}, },
"devDependencies": { "devDependencies": {
"@neutrinojs/banner": "^9.4.0",
"glob": "^7.2.0", "glob": "^7.2.0",
"mocha": "^8.3.2" "mocha": "^8.3.2"
} }

View File

@ -30,19 +30,19 @@ const resolver = (source) => (path) => {
} }
}; };
module.exports = () => (neutrino) => { module.exports = () => ({config, options}) => {
const {packageJson: {name, files = []}, source} = neutrino.options; const {packageJson: {name, files = []}, source} = options;
// index is not taken for granted. // index is not taken for granted.
neutrino.config.entryPoints.delete('index'); config.entryPoints.delete('index');
// Alias this package. // Alias this package.
neutrino.config.resolve.alias config.resolve.alias
.set(name, join(FLECKS_CORE_ROOT, 'src')); .set(name, join(FLECKS_CORE_ROOT, 'src'));
// Calculate entry points from `files`. // Calculate entry points from `files`.
files files
.filter(resolver(source)) .filter(resolver(source))
.forEach((file) => { .forEach((file) => {
const trimmed = join(dirname(file), basename(file, extname(file))); const trimmed = join(dirname(file), basename(file, extname(file)));
neutrino.config config
.entry(trimmed) .entry(trimmed)
.clear() .clear()
.add(`./src/${trimmed}`); .add(`./src/${trimmed}`);

View File

@ -1,11 +1,63 @@
const banner = require('@neutrinojs/banner');
const clean = require('@neutrinojs/clean');
const compileLoader = require('@neutrinojs/compile-loader');
const babelMerge = require('babel-merge');
const nodeExternals = require('webpack-node-externals'); const nodeExternals = require('webpack-node-externals');
module.exports = () => (neutrino) => { const R = require('./require');
const {name} = neutrino.options.packageJson;
module.exports = ({
babel = {},
targets = {
esmodules: true,
node: 'current',
},
} = {}) => (neutrino) => {
const {config, options} = neutrino;
const {name} = options.packageJson;
neutrino.use(
compileLoader({
include: [options.source, options.tests],
babel: babelMerge(
{
plugins: [R.resolve('@babel/plugin-syntax-dynamic-import')],
presets: [
[
R.resolve('@babel/preset-env'),
{
shippedProposals: true,
targets,
},
],
],
},
babel,
),
}),
);
neutrino.use(banner());
neutrino.use(clean({cleanStaleWebpackAssets: false}));
/* eslint-disable indent */ /* eslint-disable indent */
neutrino.config config
.context(options.root)
.devtool('source-map') .devtool('source-map')
.externals(nodeExternals({importType: 'umd'}))
.target('node') .target('node')
.resolve
.extensions
.merge([
'.wasm',
...options.extensions.map((ext) => `.${ext}`),
'.json',
])
.end()
.end()
.stats({
children: false,
colors: true,
entrypoints: false,
modules: false,
})
.optimization .optimization
.splitChunks(false) .splitChunks(false)
.runtimeChunk(false) .runtimeChunk(false)
@ -14,16 +66,11 @@ module.exports = () => (neutrino) => {
.filename('[name].js') .filename('[name].js')
.library(name) .library(name)
.libraryTarget('umd') .libraryTarget('umd')
.path(options.output)
.umdNamedDefine(true) .umdNamedDefine(true)
.end() .end()
.node .node
.set('__dirname', false) .set('__dirname', false)
.set('__filename', false); .set('__filename', false);
/* eslint-enable indent */ /* eslint-enable indent */
const options = neutrino.config.module
.rule('compile')
.use('babel')
.get('options');
options.presets[0][1].targets = {esmodules: true};
neutrino.config.externals(nodeExternals({importType: 'umd'}));
}; };

View File

@ -1,7 +1,6 @@
const copy = require('@neutrinojs/copy'); const copy = require('@neutrinojs/copy');
const autoentry = require('./autoentry'); const autoentry = require('./autoentry');
const fleck = require('./fleck');
const { const {
FLECKS_CORE_ROOT = process.cwd(), FLECKS_CORE_ROOT = process.cwd(),
@ -32,6 +31,5 @@ module.exports = {
pluginId: '@flecks/core.copy', pluginId: '@flecks/core.copy',
}), }),
autoentry(), autoentry(),
fleck(),
], ],
}; };

View File

@ -1,7 +1,5 @@
import {Hooks} from './flecks'; import {Hooks} from './flecks';
export {default as autoentry} from './bootstrap/autoentry';
export {default as fleck} from './bootstrap/fleck';
export {default as compose} from './compose'; export {default as compose} from './compose';
export {default as D} from './debug'; export {default as D} from './debug';
export {default as ensureUniqueReduction} from './ensure-unique-reduction'; export {default as ensureUniqueReduction} from './ensure-unique-reduction';

View File

@ -101,7 +101,6 @@ export default (program, flecks) => {
['-d, --no-production', 'dev build'], ['-d, --no-production', 'dev build'],
['-h, --hot', 'build with hot module reloading'], ['-h, --hot', 'build with hot module reloading'],
['-w, --watch', 'watch for changes'], ['-w, --watch', 'watch for changes'],
['-v, --verbose', 'verbose output'],
], ],
description: 'build', description: 'build',
action: (target, opts) => { action: (target, opts) => {
@ -109,7 +108,6 @@ export default (program, flecks) => {
hot, hot,
production, production,
watch, watch,
verbose,
} = opts; } = opts;
debug('Building...', opts); debug('Building...', opts);
const webpackConfig = flecks.buildConfig('webpack.config.js'); const webpackConfig = flecks.buildConfig('webpack.config.js');
@ -118,7 +116,6 @@ export default (program, flecks) => {
'--colors', '--colors',
'--config', webpackConfig, '--config', webpackConfig,
'--mode', (production && !hot) ? 'production' : 'development', '--mode', (production && !hot) ? 'production' : 'development',
...(verbose ? ['--stats', 'verbose'] : []),
...((watch || hot) ? ['--watch'] : []), ...((watch || hot) ? ['--watch'] : []),
]; ];
return spawnWith( return spawnWith(

View File

@ -14,6 +14,7 @@ export {
targetNeutrinos, targetNeutrinos,
} from './commands'; } from './commands';
export {default as Flecks} from './flecks'; export {default as Flecks} from './flecks';
export {default as fleck} from '../bootstrap/fleck';
export {default as require} from '../bootstrap/require'; export {default as require} from '../bootstrap/require';
export {JsonStream, transform} from './stream'; export {JsonStream, transform} from './stream';

View File

@ -32,7 +32,6 @@
], ],
"dependencies": { "dependencies": {
"@flecks/core": "^1.3.0", "@flecks/core": "^1.3.0",
"@neutrinojs/node": "^9.4.0",
"babel-merge": "^3.0.0", "babel-merge": "^3.0.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"debug": "^4.3.3", "debug": "^4.3.3",

View File

@ -1,8 +1,7 @@
const {join} = require('path'); const {join} = require('path');
const {D} = require('@flecks/core'); const {D} = require('@flecks/core');
const {Flecks} = require('@flecks/core/server'); const {fleck, Flecks} = require('@flecks/core/server');
const node = require('@neutrinojs/node');
const babelmerge = require('babel-merge'); const babelmerge = require('babel-merge');
const glob = require('glob'); const glob = require('glob');
@ -19,44 +18,28 @@ module.exports = (async () => {
const flecks = Flecks.bootstrap(); const flecks = Flecks.bootstrap();
debug('bootstrapped'); debug('bootstrapped');
const compiler = flecks.invokeFleck( // Compile.
'@flecks/fleck.compiler', const rcBabel = flecks.babel();
flecks.get('@flecks/fleck.compiler'), debug('.flecksrc: babel: %O', rcBabel);
); config.use.push(fleck({
if (compiler) { babel: babelmerge(
config.use.unshift(compiler); {configFile: flecks.buildConfig('babel.config.js')},
} ...rcBabel.map(([, babel]) => babel),
else { ),
config.use.unshift((neutrino) => { }));
neutrino.config.plugins.delete('start-server');
});
const configFile = flecks.buildConfig('babel.config.js');
config.use.unshift(node({
babel: {configFile},
clean: {
cleanStaleWebpackAssets: false,
},
}));
}
// Augment the compiler with babel config from flecksrc. config.use.push(({config}) => {
config.use.push((neutrino) => { config.stats(flecks.get('@flecks/flecks/server.stats'));
const rcBabel = flecks.babel();
debug('.flecksrc: babel: %O', rcBabel);
neutrino.config.module
.rule('compile')
.use('babel')
.tap((options) => babelmerge(options, ...rcBabel.map(([, babel]) => babel)));
}); });
config.use.push((neutrino) => { config.use.push(({config}) => {
// Test entrypoint. // Test entrypoint.
const testPaths = glob.sync(join(FLECKS_CORE_ROOT, 'test/*.js')); const testPaths = glob.sync(join(FLECKS_CORE_ROOT, 'test/*.js'));
for (let i = 0; i < flecks.platforms.length; ++i) { for (let i = 0; i < flecks.platforms.length; ++i) {
testPaths.push(...glob.sync(join(FLECKS_CORE_ROOT, `test/platforms/${flecks.platforms[i]}/*.js`))); testPaths.push(...glob.sync(join(FLECKS_CORE_ROOT, `test/platforms/${flecks.platforms[i]}/*.js`)));
} }
if (testPaths.length > 0) { if (testPaths.length > 0) {
const testEntry = neutrino.config.entry('test').clear(); const testEntry = config.entry('test').clear();
testPaths.forEach((path) => testEntry.add(path)); testPaths.forEach((path) => testEntry.add(path));
} }
}); });

View File

@ -5,6 +5,16 @@ import commands from './commands';
export default { export default {
[Hooks]: { [Hooks]: {
'@flecks/core.commands': commands, '@flecks/core.commands': commands,
'@flecks/core.config': () => ({
/**
* Webpack stats configuration when building fleck target.
*/
stats: {
chunks: false,
colors: true,
modules: false,
},
}),
'@flecks/core.targets': () => ['fleck'], '@flecks/core.targets': () => ['fleck'],
}, },
}; };

View File

@ -30,12 +30,6 @@ export default {
}, },
}, },
], ],
/**
* Define neutrino compilation middleware (e.g. @neutrinojs/react).
*/
'@flecks/http/server.compiler': () => {
return require('@neutrinojs/node');
},
/** /**
* Define middleware to run when a route is matched. * Define middleware to run when a route is matched.
*/ */
@ -66,4 +60,3 @@ export default {
}, },
}, },
}; };

View File

@ -1,12 +1,26 @@
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
const copy = require('@neutrinojs/copy'); const copy = require('@neutrinojs/copy');
const styleLoader = require('@neutrinojs/style-loader');
const nodeExternals = require('webpack-node-externals');
module.exports = (async () => { module.exports = (async () => {
// eslint-disable-next-line import/no-extraneous-dependencies, global-require // eslint-disable-next-line import/no-extraneous-dependencies, global-require
const config = await require('@flecks/fleck/server/build/fleck.neutrinorc'); const config = await require('@flecks/fleck/server/build/fleck.neutrinorc');
config.use.push(({config}) => { config.use.push(({config}) => {
config.entryPoints.delete('server/build/template'); config.entryPoints.delete('server/build/template');
config.externals(nodeExternals({
allowlist: ['mocha/mocha.css'],
importType: 'umd',
}));
}); });
config.use.push(styleLoader({
extract: {
enabled: false,
},
style: {
injectType: 'lazyStyleTag',
},
}));
config.use.push( config.use.push(
copy({ copy({
copyUnmodified: true, copyUnmodified: true,

View File

@ -38,7 +38,12 @@
], ],
"dependencies": { "dependencies": {
"@flecks/core": "^1.3.0", "@flecks/core": "^1.3.0",
"@neutrinojs/web": "^9.1.0", "@neutrinojs/dev-server": "^9.5.0",
"@neutrinojs/font-loader": "^9.5.0",
"@neutrinojs/html-loader": "^9.5.0",
"@neutrinojs/html-template": "^9.5.0",
"@neutrinojs/image-loader": "^9.5.0",
"@neutrinojs/style-loader": "^9.5.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"express": "^4.17.1", "express": "^4.17.1",
"glob": "^7.2.0", "glob": "^7.2.0",
@ -50,7 +55,8 @@
"react-dev-utils": "12.0.0", "react-dev-utils": "12.0.0",
"source-map-loader": "^1.1.3", "source-map-loader": "^1.1.3",
"webpack": "^4", "webpack": "^4",
"webpack-dev-server": "^3.11.0" "webpack-dev-server": "^3.11.0",
"webpack-node-externals": "2.5.2"
}, },
"devDependencies": { "devDependencies": {
"@flecks/fleck": "^1.3.0", "@flecks/fleck": "^1.3.0",

View File

@ -1,7 +1,6 @@
const devServer = require('@neutrinojs/dev-server');
module.exports = (flecks) => (neutrino) => { module.exports = (flecks) => (neutrino) => {
if ('production' === neutrino.config.get('mode')) {
return;
}
const { const {
devHost, devHost,
devPort, devPort,
@ -9,10 +8,11 @@ module.exports = (flecks) => (neutrino) => {
devStats, devStats,
port, port,
} = flecks.get('@flecks/http/server'); } = flecks.get('@flecks/http/server');
neutrino.config.devServer neutrino.use(devServer({
.hot(false) hot: false,
.host(devHost) host: devHost,
.port(devPort || (port + 1)) port: devPort || (port + 1),
.public(devPublic) public: devPublic,
.stats(devStats); stats: devStats,
}));
}; };

View File

@ -1,12 +1,16 @@
const {dirname, join} = require('path');
const {realpath} = require('fs/promises');
const {D} = require('@flecks/core'); const {D} = require('@flecks/core');
const {Flecks} = require('@flecks/core/server'); const {Flecks, require: R} = require('@flecks/core/server');
const web = require('@neutrinojs/web'); const htmlLoader = require('@neutrinojs/html-loader');
const htmlTemplate = require('@neutrinojs/html-template');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
const {EnvironmentPlugin} = require('webpack'); const {EnvironmentPlugin} = require('webpack');
const devServer = require('./dev-server'); const devServer = require('./dev-server');
const outputs = require('./outputs');
const runtime = require('./runtime'); const runtime = require('./runtime');
const targets = require('./targets');
const { const {
FLECKS_CORE_ROOT = process.cwd(), FLECKS_CORE_ROOT = process.cwd(),
@ -18,6 +22,96 @@ module.exports = (async () => {
debug('bootstrapping flecks...'); debug('bootstrapping flecks...');
const flecks = Flecks.bootstrap(); const flecks = Flecks.bootstrap();
debug('bootstrapped'); debug('bootstrapped');
// Build configuration.
const build = async () => {
const root = await realpath(
dirname(R.resolve(join(flecks.resolve('@flecks/http'), 'entry.js'))),
);
return (neutrino) => {
const {config, options} = neutrino;
const isProduction = 'production' === config.get('mode');
// Environment.
config
.plugin('environment')
.use(EnvironmentPlugin, [{
FLECKS_CORE_BUILD_TARGET: 'client',
}]);
// Entrypoints.
const {output: originalOutput} = options;
options.root = root;
config.context(options.root);
options.source = '.';
options.mains.index = 'entry';
options.mains.tests = {
entry: './client/tests',
title: 'Testbed',
};
options.output = join(originalOutput, flecks.get('@flecks/http/server.output'));
neutrino.use(htmlLoader());
Object.entries(options.mains).forEach(([name, mainsConfig]) => {
const {entry, ...htmlTemplateConfig} = mainsConfig;
config.entry(name).add(entry);
neutrino.use(
htmlTemplate({
pluginId: `html-${name}`,
filename: `${name}.html`,
chunks: [name],
inject: false,
template: flecks.buildConfig('template.ejs'),
...htmlTemplateConfig,
}),
);
});
// Fold in existing source maps.
config.module
.rule('maps')
.test(/\.js$/)
.enforce('pre')
.use('source-map-loader')
.loader('source-map-loader');
// Optimization.
config.optimization
.minimize(isProduction)
.splitChunks({
chunks: 'all',
name: !isProduction,
})
.runtimeChunk('single');
// Outputs.
config.output
.chunkFilename(isProduction ? 'assets/[name].[contenthash:8].js' : 'assets/[name].js')
.path(options.output)
.publicPath('/')
.filename(isProduction ? 'assets/[name].[contenthash:8].js' : 'assets/[name].js');
config
.devtool(isProduction ? 'source-map' : 'eval-source-map')
.target('web');
config.node
.set('Buffer', true)
.set('fs', 'empty')
.set('tls', 'empty')
.set('__dirname', false)
.set('__filename', false);
// Resolution.
config.resolve.extensions
.merge([
'.wasm',
...options.extensions.map((ext) => `.${ext}`),
'.json',
]);
config.resolve.modules
.merge([
join(FLECKS_CORE_ROOT, 'node_modules'),
'node_modules',
]);
// Reporting.
config.stats(flecks.get('@flecks/http/server.stats'));
// Inline the main entrypoint (nice for FCP).
config
.plugin('inline-chunks')
.use(InlineChunkHtmlPlugin, [HtmlWebpackPlugin, [/^assets\/index(\.[^.]*)?\.js$/]]);
};
};
// Neutrino configuration. // Neutrino configuration.
const config = { const config = {
options: { options: {
@ -25,48 +119,12 @@ module.exports = (async () => {
root: FLECKS_CORE_ROOT, root: FLECKS_CORE_ROOT,
}, },
use: [ use: [
({config}) => { await build(),
config
.plugin('environment')
.use(EnvironmentPlugin, [{
FLECKS_CORE_BUILD_TARGET: 'client',
}]);
},
await targets(flecks),
], ],
}; };
// Compile code.
const compiler = flecks.invokeFleck(
'@flecks/http/server.compiler',
flecks.get('@flecks/http/server.compiler'),
);
if (compiler) {
config.use.push(compiler);
}
else {
// Use neutrino's web middleware by default.
config.use.push(web({
clean: false,
hot: false,
html: {
inject: false,
template: flecks.buildConfig('template.ejs'),
},
style: {
extract: {
enabled: false,
},
style: {
injectType: 'lazyStyleTag',
},
},
}));
}
// Configure dev server. // Configure dev server.
config.use.push(devServer(flecks)); config.use.push(devServer(flecks));
// Build the client runtime. // Build the client runtime.
config.use.push(await runtime(flecks)); config.use.push(await runtime(flecks));
// Output configuration.
config.use.push(outputs());
return config; return config;
})(); })();

View File

@ -1,23 +0,0 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin');
module.exports = () => ({config}) => {
const isProduction = 'production' === config.get('mode');
// Emit node buffer builtin.
config.node
.set('Buffer', true);
// Source maps.
config.module
.rule('maps')
.test(/\.js$/)
.enforce('pre')
.use('source-map-loader')
.loader('source-map-loader');
config.devtool(isProduction ? 'source-map' : 'eval-source-map');
// Asset naming.
config.output
.chunkFilename(isProduction ? 'assets/[name].[contenthash:8].js' : 'assets/[name].js');
config
.plugin('inline-chunks')
.use(InlineChunkHtmlPlugin, [HtmlWebpackPlugin, [/^assets\/index(\.[^.]*)?\.js$/]]);
};

View File

@ -22,6 +22,7 @@ module.exports = async (flecks) => {
const importLoader = await fullresolve('@flecks/http', 'import-loader'); const importLoader = await fullresolve('@flecks/http', 'import-loader');
const tests = await realpath(R.resolve(join(flecks.resolve('@flecks/http'), 'tests'))); const tests = await realpath(R.resolve(join(flecks.resolve('@flecks/http'), 'tests')));
return (neutrino) => { return (neutrino) => {
const {config} = neutrino;
const {resolver} = httpFlecks; const {resolver} = httpFlecks;
const paths = Object.entries(resolver); const paths = Object.entries(resolver);
const source = [ const source = [
@ -53,7 +54,7 @@ module.exports = async (flecks) => {
source.push('}'); source.push('}');
source.push(''); source.push('');
// Create runtime. // Create runtime.
neutrino.config.module config.module
.rule(runtime) .rule(runtime)
.test(runtime) .test(runtime)
.use('runtime/http') .use('runtime/http')
@ -61,11 +62,11 @@ module.exports = async (flecks) => {
.options({ .options({
source: source.join('\n'), source: source.join('\n'),
}); });
neutrino.config.resolve.alias config.resolve.alias
.set('@flecks/http/runtime$', runtime); .set('@flecks/http/runtime$', runtime);
flecks.runtimeCompiler('http', neutrino); flecks.runtimeCompiler('http', neutrino);
// Handle runtime import. // Handle runtime import.
neutrino.config.module config.module
.rule(entry) .rule(entry)
.test(entry) .test(entry)
.use('entry/http') .use('entry/http')
@ -75,7 +76,7 @@ module.exports = async (flecks) => {
if (Object.keys(aliases).length > 0) { if (Object.keys(aliases).length > 0) {
Object.entries(aliases) Object.entries(aliases)
.forEach(([from, to]) => { .forEach(([from, to]) => {
neutrino.config.resolve.alias config.resolve.alias
.set(from, to); .set(from, to);
}); });
} }
@ -104,10 +105,10 @@ module.exports = async (flecks) => {
}); });
// Test entrypoint. // Test entrypoint.
if (testPaths.length > 0) { if (testPaths.length > 0) {
const testEntry = neutrino.config.entry('test').clear(); const testEntry = config.entry('test').clear();
testPaths.forEach(([, path]) => testEntry.add(path)); testPaths.forEach(([, path]) => testEntry.add(path));
} }
neutrino.config.module config.module
.rule(tests) .rule(tests)
.test(tests) .test(tests)
.use('runtime/test') .use('runtime/test')

View File

@ -1,29 +0,0 @@
/* eslint-disable no-param-reassign */
const {dirname, join} = require('path');
const {realpath} = require('fs/promises');
const {require: R} = require('@flecks/core/server');
const {
FLECKS_CORE_ROOT = process.cwd(),
} = process.env;
module.exports = async (flecks) => {
const root = await realpath(dirname(R.resolve(join(flecks.resolve('@flecks/http'), 'entry.js'))));
return (neutrino) => {
const {options} = neutrino;
const {output: originalOutput} = options;
neutrino.config.resolve.modules.merge([
join(FLECKS_CORE_ROOT, 'node_modules'),
'node_modules',
]);
options.root = root;
options.source = '.';
options.mains.index = 'entry';
options.mains.tests = {
entry: './client/tests',
title: 'Testbed',
};
options.output = join(originalOutput, flecks.get('@flecks/http/server.output'));
};
};

View File

@ -1,5 +1,8 @@
import {D, Hooks} from '@flecks/core'; import {D, Hooks} from '@flecks/core';
import {Flecks, spawnWith} from '@flecks/core/server'; import {Flecks, spawnWith} from '@flecks/core/server';
import fontLoader from '@neutrinojs/font-loader';
import imageLoader from '@neutrinojs/image-loader';
import styleLoader from '@neutrinojs/style-loader';
import {configSource, inlineConfig} from './config'; import {configSource, inlineConfig} from './config';
import {createHttpServer} from './http'; import {createHttpServer} from './http';
@ -8,6 +11,18 @@ const debug = D('@flecks/http/server');
export default { export default {
[Hooks]: { [Hooks]: {
'@flecks/core.build': (target, config) => {
config.use.push(styleLoader({
extract: {
enabled: false,
},
style: {
injectType: 'lazyStyleTag',
},
}));
config.use.push(fontLoader());
config.use.push(imageLoader());
},
'@flecks/core.build.alter': (neutrinoConfigs, flecks) => { '@flecks/core.build.alter': (neutrinoConfigs, flecks) => {
// Bail if there's no http build. // Bail if there's no http build.
if (!neutrinoConfigs.http) { if (!neutrinoConfigs.http) {
@ -60,7 +75,11 @@ export default {
/** /**
* (webpack-dev-server) Webpack stats output. * (webpack-dev-server) Webpack stats output.
*/ */
devStats: 'minimal', devStats: {
chunks: false,
colors: true,
modules: false,
},
/** /**
* Host to bind. * Host to bind.
*/ */
@ -73,6 +92,14 @@ export default {
* Port to bind. * Port to bind.
*/ */
port: 32340, port: 32340,
/**
* Webpack stats configuration when building HTTP target.
*/
stats: {
chunks: false,
colors: true,
modules: false,
},
/** /**
* Proxies to trust. * Proxies to trust.
* *

View File

@ -1,8 +1,13 @@
const {join} = require('path');
module.exports = { module.exports = {
aliases: { aliases: {
'react-dom': '@hot-loader/react-dom', 'react-dom': '@hot-loader/react-dom',
}, },
babel: { babel: {
plugins: [
join(__dirname, '..', 'style-loader'),
],
presets: [ presets: [
'@babel/preset-react', '@babel/preset-react',
], ],

View File

@ -38,12 +38,17 @@
"router/server.js.map", "router/server.js.map",
"server.js", "server.js",
"server.js.map", "server.js.map",
"src" "src",
"style-loader.js",
"style-loader.js.map"
], ],
"dependencies": { "dependencies": {
"@babel/parser": "^7.17.0",
"@babel/types": "^7.17.0",
"@flecks/core": "^1.3.0", "@flecks/core": "^1.3.0",
"@hot-loader/react-dom": "^17.0.1", "@hot-loader/react-dom": "^17.0.1",
"@neutrinojs/react": "^9.4.0", "@neutrinojs/react": "^9.4.0",
"babel-merge": "^3.0.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"history": "^5.3.0", "history": "^5.3.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",

View File

@ -1,28 +1,19 @@
import {Hooks} from '@flecks/core'; import {Hooks} from '@flecks/core';
import react from '@neutrinojs/react';
import ssr from './ssr'; import ssr from './ssr';
export default { export default {
[Hooks]: { [Hooks]: {
'@flecks/http/server.compiler': (flecks) => ( '@flecks/core.build': (target, config) => {
react({ // Resolution.
clean: false, config.use.push(({config}) => {
hot: false, config.resolve.alias
html: { .set('react-native', 'react-native-web');
inject: false, config.resolve.extensions
template: flecks.buildConfig('template.ejs'), .prepend('.web.js')
}, .prepend('.web.jsx');
style: { });
extract: { },
enabled: false,
},
style: {
injectType: 'lazyStyleTag',
},
},
})
),
'@flecks/http/server.stream.html': (stream, req, flecks) => ( '@flecks/http/server.stream.html': (stream, req, flecks) => (
flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream
), ),

View File

@ -0,0 +1,22 @@
const parser = require('@babel/parser');
const types = require('@babel/types');
let id = 0;
module.exports = () => ({
visitor: {
/* eslint-disable no-param-reassign */
ImportDeclaration(path) {
if (path.node.source.value.match(/\.s?css$/)) {
let defaultSpecifier = path.node.specifiers.find(types.isImportDefaultSpecifier);
if (!defaultSpecifier) {
defaultSpecifier = types.importDefaultSpecifier(types.identifier(`LATUS_STYLES_${id++}`));
path.node.specifiers.unshift(defaultSpecifier);
}
const {name} = defaultSpecifier.local;
path.insertAfter(parser.parse(`if ('undefined' !== typeof document) ${name}.use();`));
}
},
/* eslint-enable no-param-reassign */
},
});

View File

@ -2,12 +2,6 @@ import {Hooks} from '@flecks/core';
export default { export default {
[Hooks]: { [Hooks]: {
/**
* Define neutrino compilation middleware (e.g. @neutrinojs/react).
*/
'@flecks/server.compiler': () => {
return require('@neutrinojs/node');
},
/** /**
* Define sequential actions to run when the server comes up. * Define sequential actions to run when the server comes up.
*/ */
@ -16,4 +10,3 @@ export default {
}, },
}, },
}; };

View File

@ -35,7 +35,7 @@
"dependencies": { "dependencies": {
"@flecks/core": "^1.3.0", "@flecks/core": "^1.3.0",
"@neutrinojs/banner": "^9.5.0", "@neutrinojs/banner": "^9.5.0",
"@neutrinojs/node": "^9.4.0", "@neutrinojs/clean": "^9.5.0",
"@neutrinojs/start-server": "^9.5.0", "@neutrinojs/start-server": "^9.5.0",
"debug": "^4.3.3", "debug": "^4.3.3",
"loader-utils": "^1.4.0", "loader-utils": "^1.4.0",

View File

@ -19,6 +19,14 @@ export default {
* Whether to start the server after building. * Whether to start the server after building.
*/ */
start: true, start: true,
/**
* Webpack stats configuration when building server target.
*/
stats: {
chunks: false,
colors: true,
modules: false,
},
}), }),
}, },
}; };

View File

@ -6,13 +6,14 @@ const {require: R} = require('@flecks/core/server');
module.exports = async (flecks) => { module.exports = async (flecks) => {
const runtime = await realpath(R.resolve(join(flecks.resolve('@flecks/server'), 'runtime'))); const runtime = await realpath(R.resolve(join(flecks.resolve('@flecks/server'), 'runtime')));
return (neutrino) => { return (neutrino) => {
const {config, resolver} = flecks; const {config, options} = neutrino;
const {resolver} = flecks;
// Inject flecks configuration. // Inject flecks configuration.
const paths = Object.keys(resolver); const paths = Object.keys(resolver);
const source = [ const source = [
"process.env.FLECKS_CORE_BUILD_TARGET = 'server';", "process.env.FLECKS_CORE_BUILD_TARGET = 'server';",
'module.exports = (async () => ({', 'module.exports = (async () => ({',
` config: ${JSON.stringify(config)},`, ` config: ${JSON.stringify(flecks.config)},`,
' flecks: Object.fromEntries(await Promise.all([', ' flecks: Object.fromEntries(await Promise.all([',
paths.map((path) => ` ['${path}', import('${path}')]`).join(',\n'), paths.map((path) => ` ['${path}', import('${path}')]`).join(',\n'),
' ].map(async ([path, M]) => [path, await M]))),', ' ].map(async ([path, M]) => [path, await M]))),',
@ -28,7 +29,7 @@ module.exports = async (flecks) => {
source.push(' module.hot.addStatusHandler((status) => {'); source.push(' module.hot.addStatusHandler((status) => {');
source.push(' if ("idle" === status) {'); source.push(' if ("idle" === status) {');
source.push(' require("glob")('); source.push(' require("glob")(');
source.push(` join('${neutrino.options.output}', \`*\${previousHash}.hot-update.*\`),`); source.push(` join('${options.output}', \`*\${previousHash}.hot-update.*\`),`);
source.push(' async (error, disposing) => {'); source.push(' async (error, disposing) => {');
source.push(' if (error) {'); source.push(' if (error) {');
source.push(' throw error;'); source.push(' throw error;');
@ -49,8 +50,7 @@ module.exports = async (flecks) => {
}); });
source.push('}'); source.push('}');
// Create runtime. // Create runtime.
const entries = neutrino.config.entry('index'); config.module
neutrino.config.module
.rule(runtime) .rule(runtime)
.test(runtime) .test(runtime)
.use('runtime') .use('runtime')
@ -63,19 +63,15 @@ module.exports = async (flecks) => {
'@flecks/server/runtime', '@flecks/server/runtime',
/^@babel\/runtime\/helpers\/esm/, /^@babel\/runtime\/helpers\/esm/,
]; ];
neutrino.config.resolve.alias config.resolve.alias
.set('@flecks/server/runtime$', runtime); .set('@flecks/server/runtime$', runtime);
flecks.runtimeCompiler('server', neutrino, allowlist); flecks.runtimeCompiler('server', neutrino, allowlist);
// Rewrite to signals for HMR. // Rewrite to signals for HMR.
if ('production' !== neutrino.config.get('mode')) { if ('production' !== config.get('mode')) {
allowlist.push(/^webpack/); allowlist.push(/^webpack/);
if (entries.has(`${R.resolve('webpack/hot/poll')}?1000`)) {
entries.delete(`${R.resolve('webpack/hot/poll')}?1000`);
entries.add('webpack/hot/signal');
}
} }
// Externalize the rest. // Externalize the rest.
const nodeExternals = R('webpack-node-externals'); const nodeExternals = R('webpack-node-externals');
neutrino.config.externals(nodeExternals({allowlist})); config.externals(nodeExternals({allowlist}));
}; };
}; };

View File

@ -1,9 +1,9 @@
const {join} = require('path'); const {join} = require('path');
const {D} = require('@flecks/core'); const {D} = require('@flecks/core');
const {Flecks} = require('@flecks/core/server'); const {Flecks, require: R} = require('@flecks/core/server');
const banner = require('@neutrinojs/banner'); const banner = require('@neutrinojs/banner');
const node = require('@neutrinojs/node'); const clean = require('@neutrinojs/clean');
const startServer = require('@neutrinojs/start-server'); const startServer = require('@neutrinojs/start-server');
const runtime = require('./runtime'); const runtime = require('./runtime');
@ -27,46 +27,77 @@ module.exports = (async () => {
start: isStarting, start: isStarting,
} = flecks.get('@flecks/server'); } = flecks.get('@flecks/server');
const entry = (neutrino) => { const server = (neutrino) => {
const entries = neutrino.config.entry('index'); const {config, options} = neutrino;
entries.delete(join(FLECKS_CORE_ROOT, 'src', 'index')); const isProduction = 'production' === config.get('mode');
neutrino.use(banner());
neutrino.use(clean({cleanStaleWebpackAssets: false}));
// Entrypoints.
config.context(options.root);
const entries = config.entry('index');
if (!isProduction && hot) {
config
.plugin('hot')
.use(R.resolve('webpack/lib/HotModuleReplacementPlugin'));
entries.add('webpack/hot/signal');
}
entries.add('@flecks/server/entry'); entries.add('@flecks/server/entry');
// Fold in existing source maps.
config.module
.rule('maps')
.test(/\.js$/)
.enforce('pre')
.use('source-map-loader')
.loader('source-map-loader');
// Resolution.
config.resolve.extensions
.merge([
'.wasm',
...options.extensions.map((ext) => `.${ext}`),
'.json',
]);
// Reporting.
config.stats(flecks.get('@flecks/server.stats'));
// Outputs.
config.output
.path(options.output)
.libraryTarget('commonjs2');
config.node
.set('__dirname', false)
.set('__filename', false);
config
.devtool('source-map')
.target('node');
}; };
// Augment the application-starting configuration. // Augment the application-starting configuration.
const start = (neutrino) => { const start = (neutrino) => {
if (isStarting) { if (isStarting) {
neutrino.use(startServer({name: 'index.js'})); neutrino.use(startServer({name: 'index.js'}));
} // Really dumb that I can't just pass these in.
if (!neutrino.config.plugins.has('start-server')) { neutrino.config
return; .plugin('start-server')
} .tap((args) => {
neutrino.config const options = args[0];
.plugin('start-server') options.keyboard = false;
.tap((args) => { // HMR.
const options = args[0]; options.signal = !!hot;
options.keyboard = false; // Debugging.
// HMR. if (inspect) {
options.signal = !!hot; options.nodeArgs.push('--inspect');
// Debugging. }
if (inspect) { // Profiling.
options.nodeArgs.push('--inspect'); if (profile) {
} options.nodeArgs.push('--prof');
// Profiling. }
if (profile) { // Bail hard on unhandled rejections and report.
options.nodeArgs.push('--prof'); options.nodeArgs.push('--unhandled-rejections=strict');
} options.nodeArgs.push('--trace-uncaught');
// Bail hard on unhandled rejections and report. return args;
options.nodeArgs.push('--unhandled-rejections=strict'); });
options.nodeArgs.push('--trace-uncaught');
return args;
});
};
const compiler = flecks.invokeFleck( }
'@flecks/server.compiler', };
flecks.get('@flecks/server.compiler'),
);
const config = { const config = {
options: { options: {
@ -74,24 +105,11 @@ module.exports = (async () => {
root: FLECKS_CORE_ROOT, root: FLECKS_CORE_ROOT,
}, },
use: [ use: [
entry, server,
start, start,
], ],
}; };
if (compiler) {
config.use.unshift(compiler);
}
else {
config.use.unshift((neutrino) => {
// Default to not starting application on build.
neutrino.config.plugins.delete('start-server');
});
config.use.unshift(node({
clean: false,
hot,
}));
}
// Stub out non-server-friendly modules on the server. // Stub out non-server-friendly modules on the server.
const stubs = flecks.stubs(); const stubs = flecks.stubs();
if (Object.keys(stubs).length > 0) { if (Object.keys(stubs).length > 0) {
@ -130,8 +148,8 @@ module.exports = (async () => {
config.use.push(await runtime(flecks)); config.use.push(await runtime(flecks));
// Give the resolver a helping hand. // Give the resolver a helping hand.
config.use.push((neutrino) => { config.use.push(({config}) => {
neutrino.config.resolve.modules.merge([ config.resolve.modules.merge([
join(FLECKS_CORE_ROOT, 'node_modules'), join(FLECKS_CORE_ROOT, 'node_modules'),
'node_modules', 'node_modules',
]); ]);