From 9f08899cb520ee47966604c26054a065076ab6c7 Mon Sep 17 00:00:00 2001 From: cha0s Date: Sat, 12 Mar 2022 07:01:00 -0600 Subject: [PATCH] refactor: build --- packages/core/build/.neutrinorc.js | 27 ++-- packages/core/package.json | 4 +- packages/core/src/bootstrap/autoentry.js | 10 +- packages/core/src/bootstrap/fleck.js | 65 ++++++-- .../core/src/bootstrap/fleck.neutrinorc.js | 2 - packages/core/src/index.js | 2 - packages/core/src/server/commands.js | 3 - packages/core/src/server/index.js | 1 + packages/fleck/package.json | 1 - .../src/server/build/fleck.neutrinorc.js | 45 ++---- packages/fleck/src/server/index.js | 10 ++ packages/http/build/dox/hooks.js | 7 - packages/http/build/fleck.neutrinorc.js | 14 ++ packages/http/package.json | 10 +- packages/http/src/server/build/dev-server.js | 18 +-- .../http/src/server/build/http.neutrinorc.js | 140 +++++++++++++----- packages/http/src/server/build/outputs.js | 23 --- packages/http/src/server/build/runtime.js | 13 +- packages/http/src/server/build/targets.js | 29 ---- packages/http/src/server/index.js | 29 +++- packages/react/build/.flecksrc.js | 5 + packages/react/package.json | 7 +- packages/react/src/server.js | 29 ++-- packages/react/src/style-loader.js | 22 +++ packages/server/build/dox/hooks.js | 7 - packages/server/package.json | 2 +- packages/server/src/index.js | 8 + packages/server/src/server/build/runtime.js | 20 +-- .../src/server/build/server.neutrinorc.js | 118 ++++++++------- 29 files changed, 394 insertions(+), 277 deletions(-) delete mode 100644 packages/http/src/server/build/outputs.js delete mode 100644 packages/http/src/server/build/targets.js create mode 100644 packages/react/src/style-loader.js diff --git a/packages/core/build/.neutrinorc.js b/packages/core/build/.neutrinorc.js index f4a103e..7a8084c 100644 --- a/packages/core/build/.neutrinorc.js +++ b/packages/core/build/.neutrinorc.js @@ -4,19 +4,20 @@ const {join} = require('path'); const airbnb = require('@neutrinojs/airbnb'); const banner = require('@neutrinojs/banner'); const copy = require('@neutrinojs/copy'); -const node = require('@neutrinojs/node'); const glob = require('glob'); +const fleck = require('../src/bootstrap/fleck'); + const { FLECKS_CORE_ROOT = process.cwd(), } = process.env; -module.exports = require('../src/bootstrap/fleck.neutrinorc'); +const config = require('../src/bootstrap/fleck.neutrinorc'); // Dotfiles. -module.exports.use.push((neutrino) => { +config.use.push(({config}) => { ['eslintrc', 'eslint.defaults'].forEach((filename) => { - neutrino.config + config .entry(`server/build/.${filename}`) .clear() .add(`./src/server/build/${filename}`); @@ -24,23 +25,19 @@ module.exports.use.push((neutrino) => { }); // Tests. -module.exports.use.push((neutrino) => { +config.use.push(({config}) => { // Test entrypoint. const testPaths = glob.sync(join(FLECKS_CORE_ROOT, 'test/*.js')); testPaths.push(...glob.sync(join(FLECKS_CORE_ROOT, `test/platforms/server/*.js`))); if (testPaths.length > 0) { - const testEntry = neutrino.config.entry('test').clear(); + const testEntry = config.entry('test').clear(); testPaths.forEach((path) => testEntry.add(path)); } }); -module.exports.use.unshift((neutrino) => { - neutrino.config.plugins.delete('start-server'); -}); +config.use.unshift(fleck()); -module.exports.use.unshift(node({clean: {cleanStaleWebpackAssets: false}})); - -module.exports.use.unshift( +config.use.unshift( airbnb({ eslint: { baseConfig: { @@ -53,14 +50,14 @@ module.exports.use.unshift( }), ); -module.exports.use.push(banner({ +config.use.push(banner({ banner: '#!/usr/bin/env node', include: /^cli\.js$/, pluginId: 'shebang', raw: true, })) -module.exports.use.push(({config}) => { +config.use.push(({config}) => { config .plugin('executable') .use(class Executable { @@ -76,3 +73,5 @@ module.exports.use.push(({config}) => { }); }); + +module.exports = config; diff --git a/packages/core/package.json b/packages/core/package.json index e9cdf0e..cb6b573 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -54,9 +54,10 @@ "@babel/preset-env": "^7.12.11", "@babel/register": "^7.12.10", "@neutrinojs/airbnb": "^9.4.0", + "@neutrinojs/banner": "^9.4.0", + "@neutrinojs/clean": "^9.5.0", "@neutrinojs/compile-loader": "^9.5.0", "@neutrinojs/copy": "^9.4.0", - "@neutrinojs/node": "^9.1.0", "babel-merge": "^3.0.0", "babel-plugin-prepend": "^1.0.2", "chai": "4.2.0", @@ -80,7 +81,6 @@ "webpack-node-externals": "2.5.2" }, "devDependencies": { - "@neutrinojs/banner": "^9.4.0", "glob": "^7.2.0", "mocha": "^8.3.2" } diff --git a/packages/core/src/bootstrap/autoentry.js b/packages/core/src/bootstrap/autoentry.js index 6d4ec41..c1609f9 100644 --- a/packages/core/src/bootstrap/autoentry.js +++ b/packages/core/src/bootstrap/autoentry.js @@ -30,19 +30,19 @@ const resolver = (source) => (path) => { } }; -module.exports = () => (neutrino) => { - const {packageJson: {name, files = []}, source} = neutrino.options; +module.exports = () => ({config, options}) => { + const {packageJson: {name, files = []}, source} = options; // index is not taken for granted. - neutrino.config.entryPoints.delete('index'); + config.entryPoints.delete('index'); // Alias this package. - neutrino.config.resolve.alias + config.resolve.alias .set(name, join(FLECKS_CORE_ROOT, 'src')); // Calculate entry points from `files`. files .filter(resolver(source)) .forEach((file) => { const trimmed = join(dirname(file), basename(file, extname(file))); - neutrino.config + config .entry(trimmed) .clear() .add(`./src/${trimmed}`); diff --git a/packages/core/src/bootstrap/fleck.js b/packages/core/src/bootstrap/fleck.js index f3777da..6979340 100644 --- a/packages/core/src/bootstrap/fleck.js +++ b/packages/core/src/bootstrap/fleck.js @@ -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'); -module.exports = () => (neutrino) => { - const {name} = neutrino.options.packageJson; +const R = require('./require'); + +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 */ - neutrino.config + config + .context(options.root) .devtool('source-map') + .externals(nodeExternals({importType: 'umd'})) .target('node') + .resolve + .extensions + .merge([ + '.wasm', + ...options.extensions.map((ext) => `.${ext}`), + '.json', + ]) + .end() + .end() + .stats({ + children: false, + colors: true, + entrypoints: false, + modules: false, + }) .optimization .splitChunks(false) .runtimeChunk(false) @@ -14,16 +66,11 @@ module.exports = () => (neutrino) => { .filename('[name].js') .library(name) .libraryTarget('umd') + .path(options.output) .umdNamedDefine(true) .end() .node .set('__dirname', false) .set('__filename', false); /* 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'})); }; diff --git a/packages/core/src/bootstrap/fleck.neutrinorc.js b/packages/core/src/bootstrap/fleck.neutrinorc.js index 69765b2..535d0c1 100644 --- a/packages/core/src/bootstrap/fleck.neutrinorc.js +++ b/packages/core/src/bootstrap/fleck.neutrinorc.js @@ -1,7 +1,6 @@ const copy = require('@neutrinojs/copy'); const autoentry = require('./autoentry'); -const fleck = require('./fleck'); const { FLECKS_CORE_ROOT = process.cwd(), @@ -32,6 +31,5 @@ module.exports = { pluginId: '@flecks/core.copy', }), autoentry(), - fleck(), ], }; diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 625594d..88d8c25 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -1,7 +1,5 @@ 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 D} from './debug'; export {default as ensureUniqueReduction} from './ensure-unique-reduction'; diff --git a/packages/core/src/server/commands.js b/packages/core/src/server/commands.js index beab5c6..9df636e 100644 --- a/packages/core/src/server/commands.js +++ b/packages/core/src/server/commands.js @@ -101,7 +101,6 @@ export default (program, flecks) => { ['-d, --no-production', 'dev build'], ['-h, --hot', 'build with hot module reloading'], ['-w, --watch', 'watch for changes'], - ['-v, --verbose', 'verbose output'], ], description: 'build', action: (target, opts) => { @@ -109,7 +108,6 @@ export default (program, flecks) => { hot, production, watch, - verbose, } = opts; debug('Building...', opts); const webpackConfig = flecks.buildConfig('webpack.config.js'); @@ -118,7 +116,6 @@ export default (program, flecks) => { '--colors', '--config', webpackConfig, '--mode', (production && !hot) ? 'production' : 'development', - ...(verbose ? ['--stats', 'verbose'] : []), ...((watch || hot) ? ['--watch'] : []), ]; return spawnWith( diff --git a/packages/core/src/server/index.js b/packages/core/src/server/index.js index b9b7616..1ec466b 100644 --- a/packages/core/src/server/index.js +++ b/packages/core/src/server/index.js @@ -14,6 +14,7 @@ export { targetNeutrinos, } from './commands'; export {default as Flecks} from './flecks'; +export {default as fleck} from '../bootstrap/fleck'; export {default as require} from '../bootstrap/require'; export {JsonStream, transform} from './stream'; diff --git a/packages/fleck/package.json b/packages/fleck/package.json index eae3e43..b1cff4a 100644 --- a/packages/fleck/package.json +++ b/packages/fleck/package.json @@ -32,7 +32,6 @@ ], "dependencies": { "@flecks/core": "^1.3.0", - "@neutrinojs/node": "^9.4.0", "babel-merge": "^3.0.0", "chokidar": "^3.5.3", "debug": "^4.3.3", diff --git a/packages/fleck/src/server/build/fleck.neutrinorc.js b/packages/fleck/src/server/build/fleck.neutrinorc.js index 5bb76ca..82bfd0d 100644 --- a/packages/fleck/src/server/build/fleck.neutrinorc.js +++ b/packages/fleck/src/server/build/fleck.neutrinorc.js @@ -1,8 +1,7 @@ const {join} = require('path'); const {D} = require('@flecks/core'); -const {Flecks} = require('@flecks/core/server'); -const node = require('@neutrinojs/node'); +const {fleck, Flecks} = require('@flecks/core/server'); const babelmerge = require('babel-merge'); const glob = require('glob'); @@ -19,44 +18,28 @@ module.exports = (async () => { const flecks = Flecks.bootstrap(); debug('bootstrapped'); - const compiler = flecks.invokeFleck( - '@flecks/fleck.compiler', - flecks.get('@flecks/fleck.compiler'), - ); - if (compiler) { - config.use.unshift(compiler); - } - 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, - }, - })); - } + // Compile. + const rcBabel = flecks.babel(); + debug('.flecksrc: babel: %O', rcBabel); + config.use.push(fleck({ + babel: babelmerge( + {configFile: flecks.buildConfig('babel.config.js')}, + ...rcBabel.map(([, babel]) => babel), + ), + })); - // Augment the compiler with babel config from flecksrc. - config.use.push((neutrino) => { - 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(({config}) => { + config.stats(flecks.get('@flecks/flecks/server.stats')); }); - config.use.push((neutrino) => { + config.use.push(({config}) => { // Test entrypoint. const testPaths = glob.sync(join(FLECKS_CORE_ROOT, 'test/*.js')); for (let i = 0; i < flecks.platforms.length; ++i) { testPaths.push(...glob.sync(join(FLECKS_CORE_ROOT, `test/platforms/${flecks.platforms[i]}/*.js`))); } if (testPaths.length > 0) { - const testEntry = neutrino.config.entry('test').clear(); + const testEntry = config.entry('test').clear(); testPaths.forEach((path) => testEntry.add(path)); } }); diff --git a/packages/fleck/src/server/index.js b/packages/fleck/src/server/index.js index 966d711..c3c485d 100644 --- a/packages/fleck/src/server/index.js +++ b/packages/fleck/src/server/index.js @@ -5,6 +5,16 @@ import commands from './commands'; export default { [Hooks]: { '@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'], }, }; diff --git a/packages/http/build/dox/hooks.js b/packages/http/build/dox/hooks.js index 7235c33..81cc288 100644 --- a/packages/http/build/dox/hooks.js +++ b/packages/http/build/dox/hooks.js @@ -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. */ @@ -66,4 +60,3 @@ export default { }, }, }; - diff --git a/packages/http/build/fleck.neutrinorc.js b/packages/http/build/fleck.neutrinorc.js index 09dde28..2df5f09 100644 --- a/packages/http/build/fleck.neutrinorc.js +++ b/packages/http/build/fleck.neutrinorc.js @@ -1,12 +1,26 @@ // eslint-disable-next-line import/no-extraneous-dependencies const copy = require('@neutrinojs/copy'); +const styleLoader = require('@neutrinojs/style-loader'); +const nodeExternals = require('webpack-node-externals'); module.exports = (async () => { // eslint-disable-next-line import/no-extraneous-dependencies, global-require const config = await require('@flecks/fleck/server/build/fleck.neutrinorc'); config.use.push(({config}) => { 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( copy({ copyUnmodified: true, diff --git a/packages/http/package.json b/packages/http/package.json index 11ff32e..475ced7 100644 --- a/packages/http/package.json +++ b/packages/http/package.json @@ -38,7 +38,12 @@ ], "dependencies": { "@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", "express": "^4.17.1", "glob": "^7.2.0", @@ -50,7 +55,8 @@ "react-dev-utils": "12.0.0", "source-map-loader": "^1.1.3", "webpack": "^4", - "webpack-dev-server": "^3.11.0" + "webpack-dev-server": "^3.11.0", + "webpack-node-externals": "2.5.2" }, "devDependencies": { "@flecks/fleck": "^1.3.0", diff --git a/packages/http/src/server/build/dev-server.js b/packages/http/src/server/build/dev-server.js index a1b9de2..b0c20fe 100644 --- a/packages/http/src/server/build/dev-server.js +++ b/packages/http/src/server/build/dev-server.js @@ -1,7 +1,6 @@ +const devServer = require('@neutrinojs/dev-server'); + module.exports = (flecks) => (neutrino) => { - if ('production' === neutrino.config.get('mode')) { - return; - } const { devHost, devPort, @@ -9,10 +8,11 @@ module.exports = (flecks) => (neutrino) => { devStats, port, } = flecks.get('@flecks/http/server'); - neutrino.config.devServer - .hot(false) - .host(devHost) - .port(devPort || (port + 1)) - .public(devPublic) - .stats(devStats); + neutrino.use(devServer({ + hot: false, + host: devHost, + port: devPort || (port + 1), + public: devPublic, + stats: devStats, + })); }; diff --git a/packages/http/src/server/build/http.neutrinorc.js b/packages/http/src/server/build/http.neutrinorc.js index 128409b..6f16138 100644 --- a/packages/http/src/server/build/http.neutrinorc.js +++ b/packages/http/src/server/build/http.neutrinorc.js @@ -1,12 +1,16 @@ +const {dirname, join} = require('path'); +const {realpath} = require('fs/promises'); + const {D} = require('@flecks/core'); -const {Flecks} = require('@flecks/core/server'); -const web = require('@neutrinojs/web'); +const {Flecks, require: R} = require('@flecks/core/server'); +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 devServer = require('./dev-server'); -const outputs = require('./outputs'); const runtime = require('./runtime'); -const targets = require('./targets'); const { FLECKS_CORE_ROOT = process.cwd(), @@ -18,6 +22,96 @@ module.exports = (async () => { debug('bootstrapping flecks...'); const flecks = Flecks.bootstrap(); 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. const config = { options: { @@ -25,48 +119,12 @@ module.exports = (async () => { root: FLECKS_CORE_ROOT, }, use: [ - ({config}) => { - config - .plugin('environment') - .use(EnvironmentPlugin, [{ - FLECKS_CORE_BUILD_TARGET: 'client', - }]); - }, - await targets(flecks), + await build(), ], }; - // 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. config.use.push(devServer(flecks)); // Build the client runtime. config.use.push(await runtime(flecks)); - // Output configuration. - config.use.push(outputs()); return config; })(); diff --git a/packages/http/src/server/build/outputs.js b/packages/http/src/server/build/outputs.js deleted file mode 100644 index ec2f370..0000000 --- a/packages/http/src/server/build/outputs.js +++ /dev/null @@ -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$/]]); -}; diff --git a/packages/http/src/server/build/runtime.js b/packages/http/src/server/build/runtime.js index 2715a8a..105eb45 100644 --- a/packages/http/src/server/build/runtime.js +++ b/packages/http/src/server/build/runtime.js @@ -22,6 +22,7 @@ module.exports = async (flecks) => { const importLoader = await fullresolve('@flecks/http', 'import-loader'); const tests = await realpath(R.resolve(join(flecks.resolve('@flecks/http'), 'tests'))); return (neutrino) => { + const {config} = neutrino; const {resolver} = httpFlecks; const paths = Object.entries(resolver); const source = [ @@ -53,7 +54,7 @@ module.exports = async (flecks) => { source.push('}'); source.push(''); // Create runtime. - neutrino.config.module + config.module .rule(runtime) .test(runtime) .use('runtime/http') @@ -61,11 +62,11 @@ module.exports = async (flecks) => { .options({ source: source.join('\n'), }); - neutrino.config.resolve.alias + config.resolve.alias .set('@flecks/http/runtime$', runtime); flecks.runtimeCompiler('http', neutrino); // Handle runtime import. - neutrino.config.module + config.module .rule(entry) .test(entry) .use('entry/http') @@ -75,7 +76,7 @@ module.exports = async (flecks) => { if (Object.keys(aliases).length > 0) { Object.entries(aliases) .forEach(([from, to]) => { - neutrino.config.resolve.alias + config.resolve.alias .set(from, to); }); } @@ -104,10 +105,10 @@ module.exports = async (flecks) => { }); // Test entrypoint. if (testPaths.length > 0) { - const testEntry = neutrino.config.entry('test').clear(); + const testEntry = config.entry('test').clear(); testPaths.forEach(([, path]) => testEntry.add(path)); } - neutrino.config.module + config.module .rule(tests) .test(tests) .use('runtime/test') diff --git a/packages/http/src/server/build/targets.js b/packages/http/src/server/build/targets.js deleted file mode 100644 index 61da9d0..0000000 --- a/packages/http/src/server/build/targets.js +++ /dev/null @@ -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')); - }; -}; diff --git a/packages/http/src/server/index.js b/packages/http/src/server/index.js index d7c191f..b8df037 100644 --- a/packages/http/src/server/index.js +++ b/packages/http/src/server/index.js @@ -1,5 +1,8 @@ import {D, Hooks} from '@flecks/core'; 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 {createHttpServer} from './http'; @@ -8,6 +11,18 @@ const debug = D('@flecks/http/server'); export default { [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) => { // Bail if there's no http build. if (!neutrinoConfigs.http) { @@ -60,7 +75,11 @@ export default { /** * (webpack-dev-server) Webpack stats output. */ - devStats: 'minimal', + devStats: { + chunks: false, + colors: true, + modules: false, + }, /** * Host to bind. */ @@ -73,6 +92,14 @@ export default { * Port to bind. */ port: 32340, + /** + * Webpack stats configuration when building HTTP target. + */ + stats: { + chunks: false, + colors: true, + modules: false, + }, /** * Proxies to trust. * diff --git a/packages/react/build/.flecksrc.js b/packages/react/build/.flecksrc.js index 7577e18..e07048a 100644 --- a/packages/react/build/.flecksrc.js +++ b/packages/react/build/.flecksrc.js @@ -1,8 +1,13 @@ +const {join} = require('path'); + module.exports = { aliases: { 'react-dom': '@hot-loader/react-dom', }, babel: { + plugins: [ + join(__dirname, '..', 'style-loader'), + ], presets: [ '@babel/preset-react', ], diff --git a/packages/react/package.json b/packages/react/package.json index 35261d6..a106aa8 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -38,12 +38,17 @@ "router/server.js.map", "server.js", "server.js.map", - "src" + "src", + "style-loader.js", + "style-loader.js.map" ], "dependencies": { + "@babel/parser": "^7.17.0", + "@babel/types": "^7.17.0", "@flecks/core": "^1.3.0", "@hot-loader/react-dom": "^17.0.1", "@neutrinojs/react": "^9.4.0", + "babel-merge": "^3.0.0", "classnames": "^2.3.1", "history": "^5.3.0", "prop-types": "^15.7.2", diff --git a/packages/react/src/server.js b/packages/react/src/server.js index f571413..900582d 100644 --- a/packages/react/src/server.js +++ b/packages/react/src/server.js @@ -1,28 +1,19 @@ import {Hooks} from '@flecks/core'; -import react from '@neutrinojs/react'; import ssr from './ssr'; export default { [Hooks]: { - '@flecks/http/server.compiler': (flecks) => ( - react({ - clean: false, - hot: false, - html: { - inject: false, - template: flecks.buildConfig('template.ejs'), - }, - style: { - extract: { - enabled: false, - }, - style: { - injectType: 'lazyStyleTag', - }, - }, - }) - ), + '@flecks/core.build': (target, config) => { + // Resolution. + config.use.push(({config}) => { + config.resolve.alias + .set('react-native', 'react-native-web'); + config.resolve.extensions + .prepend('.web.js') + .prepend('.web.jsx'); + }); + }, '@flecks/http/server.stream.html': (stream, req, flecks) => ( flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream ), diff --git a/packages/react/src/style-loader.js b/packages/react/src/style-loader.js new file mode 100644 index 0000000..78052db --- /dev/null +++ b/packages/react/src/style-loader.js @@ -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 */ + }, +}); diff --git a/packages/server/build/dox/hooks.js b/packages/server/build/dox/hooks.js index 534660d..b6f1ac0 100644 --- a/packages/server/build/dox/hooks.js +++ b/packages/server/build/dox/hooks.js @@ -2,12 +2,6 @@ import {Hooks} from '@flecks/core'; export default { [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. */ @@ -16,4 +10,3 @@ export default { }, }, }; - diff --git a/packages/server/package.json b/packages/server/package.json index e6c3c5d..6c7624d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -35,7 +35,7 @@ "dependencies": { "@flecks/core": "^1.3.0", "@neutrinojs/banner": "^9.5.0", - "@neutrinojs/node": "^9.4.0", + "@neutrinojs/clean": "^9.5.0", "@neutrinojs/start-server": "^9.5.0", "debug": "^4.3.3", "loader-utils": "^1.4.0", diff --git a/packages/server/src/index.js b/packages/server/src/index.js index 798b416..6be0ca5 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -19,6 +19,14 @@ export default { * Whether to start the server after building. */ start: true, + /** + * Webpack stats configuration when building server target. + */ + stats: { + chunks: false, + colors: true, + modules: false, + }, }), }, }; diff --git a/packages/server/src/server/build/runtime.js b/packages/server/src/server/build/runtime.js index 69f120e..83825db 100644 --- a/packages/server/src/server/build/runtime.js +++ b/packages/server/src/server/build/runtime.js @@ -6,13 +6,14 @@ const {require: R} = require('@flecks/core/server'); module.exports = async (flecks) => { const runtime = await realpath(R.resolve(join(flecks.resolve('@flecks/server'), 'runtime'))); return (neutrino) => { - const {config, resolver} = flecks; + const {config, options} = neutrino; + const {resolver} = flecks; // Inject flecks configuration. const paths = Object.keys(resolver); const source = [ "process.env.FLECKS_CORE_BUILD_TARGET = 'server';", 'module.exports = (async () => ({', - ` config: ${JSON.stringify(config)},`, + ` config: ${JSON.stringify(flecks.config)},`, ' flecks: Object.fromEntries(await Promise.all([', paths.map((path) => ` ['${path}', import('${path}')]`).join(',\n'), ' ].map(async ([path, M]) => [path, await M]))),', @@ -28,7 +29,7 @@ module.exports = async (flecks) => { source.push(' module.hot.addStatusHandler((status) => {'); source.push(' if ("idle" === status) {'); 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(' if (error) {'); source.push(' throw error;'); @@ -49,8 +50,7 @@ module.exports = async (flecks) => { }); source.push('}'); // Create runtime. - const entries = neutrino.config.entry('index'); - neutrino.config.module + config.module .rule(runtime) .test(runtime) .use('runtime') @@ -63,19 +63,15 @@ module.exports = async (flecks) => { '@flecks/server/runtime', /^@babel\/runtime\/helpers\/esm/, ]; - neutrino.config.resolve.alias + config.resolve.alias .set('@flecks/server/runtime$', runtime); flecks.runtimeCompiler('server', neutrino, allowlist); // Rewrite to signals for HMR. - if ('production' !== neutrino.config.get('mode')) { + if ('production' !== config.get('mode')) { 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. const nodeExternals = R('webpack-node-externals'); - neutrino.config.externals(nodeExternals({allowlist})); + config.externals(nodeExternals({allowlist})); }; }; diff --git a/packages/server/src/server/build/server.neutrinorc.js b/packages/server/src/server/build/server.neutrinorc.js index 6cdf2cb..e42f615 100644 --- a/packages/server/src/server/build/server.neutrinorc.js +++ b/packages/server/src/server/build/server.neutrinorc.js @@ -1,9 +1,9 @@ const {join} = require('path'); 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 node = require('@neutrinojs/node'); +const clean = require('@neutrinojs/clean'); const startServer = require('@neutrinojs/start-server'); const runtime = require('./runtime'); @@ -27,46 +27,77 @@ module.exports = (async () => { start: isStarting, } = flecks.get('@flecks/server'); - const entry = (neutrino) => { - const entries = neutrino.config.entry('index'); - entries.delete(join(FLECKS_CORE_ROOT, 'src', 'index')); + const server = (neutrino) => { + const {config, options} = neutrino; + 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'); + // 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. const start = (neutrino) => { if (isStarting) { neutrino.use(startServer({name: 'index.js'})); - } - if (!neutrino.config.plugins.has('start-server')) { - return; - } - neutrino.config - .plugin('start-server') - .tap((args) => { - const options = args[0]; - options.keyboard = false; - // HMR. - options.signal = !!hot; - // Debugging. - if (inspect) { - options.nodeArgs.push('--inspect'); - } - // Profiling. - if (profile) { - options.nodeArgs.push('--prof'); - } - // Bail hard on unhandled rejections and report. - options.nodeArgs.push('--unhandled-rejections=strict'); - options.nodeArgs.push('--trace-uncaught'); - return args; - }); - }; + // Really dumb that I can't just pass these in. + neutrino.config + .plugin('start-server') + .tap((args) => { + const options = args[0]; + options.keyboard = false; + // HMR. + options.signal = !!hot; + // Debugging. + if (inspect) { + options.nodeArgs.push('--inspect'); + } + // Profiling. + if (profile) { + options.nodeArgs.push('--prof'); + } + // Bail hard on unhandled rejections and report. + 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 = { options: { @@ -74,24 +105,11 @@ module.exports = (async () => { root: FLECKS_CORE_ROOT, }, use: [ - entry, + server, 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. const stubs = flecks.stubs(); if (Object.keys(stubs).length > 0) { @@ -130,8 +148,8 @@ module.exports = (async () => { config.use.push(await runtime(flecks)); // Give the resolver a helping hand. - config.use.push((neutrino) => { - neutrino.config.resolve.modules.merge([ + config.use.push(({config}) => { + config.resolve.modules.merge([ join(FLECKS_CORE_ROOT, 'node_modules'), 'node_modules', ]);