refactor: explication
This commit is contained in:
parent
85958c2d87
commit
f657c5ea8b
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"eslint.workingDirectories": [{"pattern": "./packages/*"}],
|
||||
"eslint.options": {"overrideConfigFile": "./node_modules/@flecks/build/build/eslint.config.js"},
|
||||
"eslint.options": {"overrideConfigFile": "../build/dist/build/eslint.config.js"},
|
||||
}
|
|
@ -10,6 +10,7 @@ module.exports = (api) => {
|
|||
setSpreadProperties: true,
|
||||
},
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-class-properties',
|
||||
'@babel/plugin-syntax-logical-assignment-operators',
|
||||
'@babel/plugin-syntax-nullish-coalescing-operator',
|
||||
|
@ -24,6 +25,11 @@ module.exports = (api) => {
|
|||
'@babel/plugin-transform-async-to-generator',
|
||||
'@babel/plugin-transform-object-super',
|
||||
],
|
||||
shippedProposals: true,
|
||||
targets: {
|
||||
esmodules: true,
|
||||
node: 'current',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const {realpath} = require('fs/promises');
|
||||
const {dirname, join, relative} = require('path');
|
||||
const {join} = require('path');
|
||||
|
||||
const D = require('@flecks/core/build/debug');
|
||||
const {Flecks} = require('@flecks/core/build/flecks');
|
||||
|
@ -65,142 +64,44 @@ module.exports = class Build extends Flecks {
|
|||
roots = {};
|
||||
|
||||
async babel() {
|
||||
const merging = [
|
||||
{
|
||||
plugins: ['@babel/plugin-syntax-dynamic-import'],
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
shippedProposals: true,
|
||||
targets: {
|
||||
esmodules: true,
|
||||
node: 'current',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
merging.push({configFile: await this.resolveBuildConfig('babel.config.js')});
|
||||
merging.push(...this.invokeFlat('@flecks/core.babel'));
|
||||
return babelmerge.all(merging);
|
||||
return babelmerge.all([
|
||||
{configFile: await this.resolveBuildConfig('babel.config.js')},
|
||||
...this.invokeFlat('@flecks/core.babel'),
|
||||
]);
|
||||
}
|
||||
|
||||
static async buildRuntime(originalConfig, platforms, flecks = {}) {
|
||||
const dealiasedConfig = Object.fromEntries(
|
||||
Object.entries(originalConfig)
|
||||
.map(([maybeAliasedPath, config]) => {
|
||||
const index = maybeAliasedPath.indexOf(':');
|
||||
return [
|
||||
-1 === index ? maybeAliasedPath : maybeAliasedPath.slice(0, index),
|
||||
config,
|
||||
];
|
||||
}),
|
||||
);
|
||||
const resolver = new Resolver();
|
||||
const explication = await explicate(
|
||||
Object.keys(originalConfig),
|
||||
{
|
||||
platforms,
|
||||
resolver,
|
||||
root: FLECKS_CORE_ROOT,
|
||||
importer: (request) => require(request),
|
||||
},
|
||||
);
|
||||
const runtime = {
|
||||
config: environmentConfiguration(
|
||||
Object.fromEntries(
|
||||
Object.values(explication.descriptors)
|
||||
.map(({path}) => [path, dealiasedConfig[path] || {}]),
|
||||
),
|
||||
const cleanConfig = JSON.parse(JSON.stringify(originalConfig));
|
||||
// Dealias the config keys.
|
||||
const dealiasedConfig = environmentConfiguration(
|
||||
Object.fromEntries(
|
||||
Object.entries(cleanConfig)
|
||||
.map(([maybeAliasedPath, config]) => {
|
||||
const index = maybeAliasedPath.indexOf(':');
|
||||
return [
|
||||
-1 === index ? maybeAliasedPath : maybeAliasedPath.slice(0, index),
|
||||
config,
|
||||
];
|
||||
}),
|
||||
),
|
||||
flecks: Object.fromEntries(
|
||||
Object.values(explication.descriptors)
|
||||
.map(({path, request}) => [path, flecks[path] || explication.roots[request] || {}]),
|
||||
),
|
||||
};
|
||||
const aliased = {};
|
||||
const compiled = {};
|
||||
const reverseRequest = Object.fromEntries(
|
||||
Object.entries(explication.descriptors)
|
||||
.map(([, {path, request}]) => [request, path]),
|
||||
);
|
||||
const roots = Object.fromEntries(
|
||||
(await Promise.all(Object.entries(explication.roots)
|
||||
.map(async ([request, bootstrap]) => {
|
||||
const packageRequest = await realpath(await resolver.resolve(join(request, 'package.json')));
|
||||
const realDirname = dirname(packageRequest);
|
||||
const {dependencies = {}, devDependencies = {}} = require(packageRequest);
|
||||
let source;
|
||||
let root;
|
||||
// One of ours?
|
||||
if (
|
||||
[].concat(
|
||||
Object.keys(dependencies),
|
||||
Object.keys(devDependencies),
|
||||
)
|
||||
.includes('@flecks/fleck')
|
||||
) {
|
||||
root = realDirname.endsWith('/dist') ? realDirname.slice(0, -5) : realDirname;
|
||||
source = join(root, 'src');
|
||||
}
|
||||
else {
|
||||
root = realDirname;
|
||||
source = realDirname;
|
||||
}
|
||||
return [
|
||||
reverseRequest[request],
|
||||
{
|
||||
bootstrap,
|
||||
request,
|
||||
root,
|
||||
source,
|
||||
},
|
||||
];
|
||||
})))
|
||||
// Reverse sort for greedy root matching.
|
||||
.sort(([l], [r]) => (l < r ? 1 : -1)),
|
||||
);
|
||||
await Promise.all(
|
||||
Object.entries(explication.descriptors)
|
||||
.map(async ([, {path, request}]) => {
|
||||
if (path !== request) {
|
||||
aliased[path] = request;
|
||||
}
|
||||
const [root, requestRoot] = Object.entries(roots)
|
||||
.find(([, {request: rootRequest}]) => request.startsWith(rootRequest)) || [];
|
||||
if (requestRoot && compiled[requestRoot.root]) {
|
||||
return;
|
||||
}
|
||||
let resolvedRequest = await resolver.resolve(request);
|
||||
if (!resolvedRequest) {
|
||||
if (!requestRoot) {
|
||||
return;
|
||||
}
|
||||
resolvedRequest = await resolver.resolve(join(requestRoot.root, 'package.json'));
|
||||
}
|
||||
const realResolvedRequest = await realpath(resolvedRequest);
|
||||
if (path !== request || resolvedRequest !== realResolvedRequest) {
|
||||
if (requestRoot) {
|
||||
if (!compiled[requestRoot.root]) {
|
||||
compiled[requestRoot.root] = {
|
||||
flecks: [],
|
||||
path: root,
|
||||
root: requestRoot.root,
|
||||
source: requestRoot.source,
|
||||
};
|
||||
}
|
||||
compiled[requestRoot.root].flecks.push(path);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
return {
|
||||
aliased,
|
||||
compiled,
|
||||
const resolver = new Resolver({root: FLECKS_CORE_ROOT});
|
||||
const {paths, roots} = await explicate({
|
||||
paths: Object.keys(originalConfig),
|
||||
platforms,
|
||||
resolver,
|
||||
roots,
|
||||
importer: (request) => require(request),
|
||||
});
|
||||
const runtime = {
|
||||
config: Object.fromEntries(paths.map((path) => [path, dealiasedConfig[path] || {}])),
|
||||
flecks: Object.fromEntries(paths.map((path) => [
|
||||
path,
|
||||
flecks[path] || roots[path]?.bootstrap || {},
|
||||
])),
|
||||
};
|
||||
return {
|
||||
resolver,
|
||||
roots: Object.entries(roots).map(([path, {request}]) => [path, request]),
|
||||
runtime,
|
||||
};
|
||||
}
|
||||
|
@ -225,15 +126,11 @@ module.exports = class Build extends Flecks {
|
|||
debug('bootstrap configuration (%s)', configType);
|
||||
debugSilly(originalConfig);
|
||||
const {
|
||||
aliased,
|
||||
compiled,
|
||||
resolver,
|
||||
roots,
|
||||
runtime,
|
||||
} = await this.buildRuntime(originalConfig, platforms, configFlecks);
|
||||
const flecks = super.from(runtime);
|
||||
flecks.aliased = aliased;
|
||||
flecks.compiled = compiled;
|
||||
flecks.platforms = platforms;
|
||||
flecks.roots = roots;
|
||||
flecks.resolver = resolver;
|
||||
|
@ -255,7 +152,7 @@ module.exports = class Build extends Flecks {
|
|||
return Object.fromEntries(
|
||||
Object.entries(this.config)
|
||||
.map(([path, config]) => {
|
||||
const alias = this.aliased[path];
|
||||
const alias = this.resolver.fallbacks[path];
|
||||
return [alias ? `${path}:${alias}` : path, config];
|
||||
}),
|
||||
);
|
||||
|
@ -279,38 +176,15 @@ module.exports = class Build extends Flecks {
|
|||
return this.resolver.resolve(join(fleck, 'build', config));
|
||||
}
|
||||
|
||||
async runtimeCompiler(runtime, config, {additionalModuleDirs = [], allowlist = []} = {}) {
|
||||
async runtimeCompiler(runtime, config) {
|
||||
// Compile?
|
||||
const needCompilation = Object.entries(this.compiled);
|
||||
if (needCompilation.length > 0) {
|
||||
const babelConfig = await this.babel();
|
||||
const includes = [];
|
||||
// Alias and de-externalize.
|
||||
await Promise.all(
|
||||
needCompilation
|
||||
.map(async ([
|
||||
root,
|
||||
{
|
||||
path,
|
||||
source,
|
||||
},
|
||||
]) => {
|
||||
allowlist.push(new RegExp(`^${path}`));
|
||||
debugSilly('%s runtime de-externalized %s, alias: %s', runtime, root, source || path);
|
||||
// Alias.
|
||||
config.resolve.alias[path] = source || path;
|
||||
// Root aliases.
|
||||
config.resolve.fallback[path] = root;
|
||||
config.resolve.modules.push(relative(FLECKS_CORE_ROOT, join(root, 'node_modules')));
|
||||
additionalModuleDirs.push(relative(FLECKS_CORE_ROOT, join(root, 'node_modules')));
|
||||
includes.push(root);
|
||||
}),
|
||||
);
|
||||
// Compile.
|
||||
const compiled = this.roots.filter(([path, request]) => path !== request);
|
||||
if (compiled.length > 0) {
|
||||
const include = Object.values(this.resolver.aliases);
|
||||
config.module.rules.push(
|
||||
{
|
||||
test: /\.(m?jsx?)?$/,
|
||||
include: includes,
|
||||
include,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve('babel-loader'),
|
||||
|
@ -318,35 +192,30 @@ module.exports = class Build extends Flecks {
|
|||
cacheDirectory: true,
|
||||
babelrc: false,
|
||||
configFile: false,
|
||||
...babelConfig,
|
||||
...await this.babel(),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
// Aliases.
|
||||
Object.entries(this.aliased)
|
||||
.forEach(([from, to]) => {
|
||||
if (
|
||||
!Object.entries(this.compiled)
|
||||
.some(([, {flecks}]) => flecks.includes(from))
|
||||
) {
|
||||
config.resolve.alias[from] = to;
|
||||
}
|
||||
});
|
||||
// Our very own lil' chunk.
|
||||
Flecks.set(config, 'optimization.splitChunks.cacheGroups.flecks-compiled', {
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
priority: 100,
|
||||
test: new RegExp(
|
||||
`(?:${
|
||||
includes
|
||||
.map((path) => path.replace(/[\\/]/g, '[\\/]')).join('|')
|
||||
})`,
|
||||
),
|
||||
test: new RegExp(`(?:${
|
||||
include.map((path) => path.replace(/[\\/]/g, '[\\/]')).join('|')
|
||||
})`),
|
||||
});
|
||||
}
|
||||
// Resolution.
|
||||
const {resolve, resolveLoader} = config;
|
||||
resolve.alias = {...resolve.alias, ...this.resolver.aliases};
|
||||
resolve.fallback = {...resolve.fallback, ...this.resolver.fallbacks};
|
||||
resolve.modules = [...resolve.modules, ...this.resolver.modules];
|
||||
resolveLoader.alias = {...resolveLoader.alias, ...this.resolver.aliases};
|
||||
resolveLoader.fallback = {...resolveLoader.fallback, ...this.resolver.fallbacks};
|
||||
resolveLoader.modules = [...resolveLoader.modules, ...this.resolver.modules];
|
||||
}
|
||||
|
||||
get stubs() {
|
||||
|
|
|
@ -9,7 +9,7 @@ module.exports = async (env, argv) => {
|
|||
config.plugins.push(new ProcessAssets('fleck', flecks));
|
||||
// Small hack because internals.
|
||||
flecks.hooks['@flecks/build.processAssets'] = [{
|
||||
hook: '@flecks/build',
|
||||
fleck: '@flecks/build',
|
||||
fn: (target, assets, compilation) => processFleckAssets(assets, compilation),
|
||||
}];
|
||||
config.plugins.push(executable());
|
||||
|
|
|
@ -1,159 +1,127 @@
|
|||
const {join, relative, resolve} = require('path');
|
||||
|
||||
const {access, realpath} = require('fs/promises');
|
||||
const {Module} = require('module');
|
||||
const {
|
||||
FLECKS_CORE_ROOT = process.cwd(),
|
||||
} = process.env;
|
||||
delimiter,
|
||||
join,
|
||||
resolve,
|
||||
} = require('path');
|
||||
|
||||
module.exports = async function explicate(
|
||||
maybeAliasedPaths,
|
||||
{
|
||||
importer,
|
||||
paths: maybeAliasedPaths,
|
||||
platforms = ['server'],
|
||||
resolver,
|
||||
root,
|
||||
},
|
||||
) {
|
||||
const descriptors = {};
|
||||
const seen = {};
|
||||
const dependentPaths = [];
|
||||
const roots = {};
|
||||
function createDescriptor(maybeAliasedPath) {
|
||||
const index = maybeAliasedPath.indexOf(':');
|
||||
return -1 === index
|
||||
? {
|
||||
path: maybeAliasedPath,
|
||||
request: maybeAliasedPath,
|
||||
}
|
||||
: {
|
||||
path: maybeAliasedPath.slice(0, index),
|
||||
request: resolve(root, maybeAliasedPath.slice(index + 1)),
|
||||
};
|
||||
}
|
||||
async function doExplication(descriptor) {
|
||||
const {path, request} = descriptor;
|
||||
if (
|
||||
platforms
|
||||
.filter((platform) => platform.startsWith('!'))
|
||||
.map((platform) => platform.slice(1))
|
||||
.includes(path.split('/').pop())
|
||||
) {
|
||||
async function addRoot(path, request) {
|
||||
// Already added it?
|
||||
if (Object.keys(roots).some((rootPath) => path.startsWith(rootPath))) {
|
||||
return;
|
||||
}
|
||||
descriptors[request] = descriptor;
|
||||
}
|
||||
async function getRootDescriptor(descriptor) {
|
||||
const {path, request} = descriptor;
|
||||
// Walk up and find the root, if any.
|
||||
const pathParts = path.split('/');
|
||||
const requestParts = request.split('/');
|
||||
let rootDescriptor;
|
||||
const requestParts = path === request
|
||||
? pathParts.slice()
|
||||
: resolve(resolver.root, request).split('/');
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (pathParts.length > 0 && requestParts.length > 0) {
|
||||
const candidate = requestParts.join('/');
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
if (await resolver.resolve(join(candidate, 'package.json'))) {
|
||||
rootDescriptor = {
|
||||
path: pathParts.join('/'),
|
||||
request: requestParts.join('/'),
|
||||
const rootPath = pathParts.join('/');
|
||||
// Don't add the root if this path doesn't actually exist.
|
||||
if (path !== rootPath && !await resolver.resolve(path)) {
|
||||
break;
|
||||
}
|
||||
// Resolve symlinks.
|
||||
let realCandidate;
|
||||
try {
|
||||
realCandidate = await realpath(candidate);
|
||||
}
|
||||
catch (error) {
|
||||
realCandidate = candidate;
|
||||
}
|
||||
// Aliased or symlinked? Include submodules.
|
||||
if (path !== request || realCandidate !== candidate) {
|
||||
const submodules = join(realCandidate, 'node_modules');
|
||||
resolver.addModules(submodules);
|
||||
// Runtime NODE_PATH hacking.
|
||||
const {env} = process;
|
||||
env.NODE_PATH = (env.NODE_PATH || '') + delimiter + submodules;
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
Module._initPaths();
|
||||
}
|
||||
// Load `bootstrap.js`.
|
||||
const bootstrapPath = await resolver.resolve(join(candidate, 'build', 'flecks.bootstrap'));
|
||||
const bootstrap = bootstrapPath ? importer(bootstrapPath) : {};
|
||||
// First add dependencies.
|
||||
const {dependencies = []} = bootstrap;
|
||||
if (dependencies.length > 0) {
|
||||
await Promise.all(dependencies.map((dependency) => addRoot(dependency, dependency)));
|
||||
dependentPaths.push(...dependencies);
|
||||
}
|
||||
// Add root as a dependency.
|
||||
dependentPaths.push(rootPath);
|
||||
// Add root.
|
||||
roots[rootPath] = {
|
||||
bootstrap,
|
||||
request: realCandidate !== candidate ? realCandidate : candidate,
|
||||
};
|
||||
break;
|
||||
}
|
||||
pathParts.pop();
|
||||
requestParts.pop();
|
||||
}
|
||||
return rootDescriptor;
|
||||
/* eslint-enable no-await-in-loop */
|
||||
}
|
||||
function descriptorsAreTheSame(l, r) {
|
||||
return (l && !r) || (!l && r) ? false : l.request === r.request;
|
||||
}
|
||||
async function explicateDescriptor(descriptor) {
|
||||
if (descriptors[descriptor.request] || seen[descriptor.request]) {
|
||||
return;
|
||||
}
|
||||
seen[descriptor.request] = true;
|
||||
const areDescriptorsTheSame = descriptorsAreTheSame(
|
||||
descriptor,
|
||||
await getRootDescriptor(descriptor),
|
||||
);
|
||||
const resolved = await resolver.resolve(descriptor.request);
|
||||
if (resolved || areDescriptorsTheSame) {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
await explicateRoot(descriptor);
|
||||
}
|
||||
if (!resolved && areDescriptorsTheSame) {
|
||||
descriptors[descriptor.request] = descriptor;
|
||||
}
|
||||
if (resolved) {
|
||||
await doExplication(descriptor);
|
||||
}
|
||||
let descriptorRequest = descriptor.request;
|
||||
if (areDescriptorsTheSame) {
|
||||
descriptorRequest = join(descriptorRequest, 'src');
|
||||
}
|
||||
if (descriptor.path !== descriptor.request) {
|
||||
resolver.addAlias(descriptor.path, descriptorRequest);
|
||||
if (descriptorRequest !== descriptor.request) {
|
||||
resolver.addFallback(descriptor.path, descriptor.request);
|
||||
}
|
||||
}
|
||||
await Promise.all(
|
||||
platforms
|
||||
.filter((platform) => !platform.startsWith('!'))
|
||||
.map(async (platform) => {
|
||||
if (await resolver.resolve(join(descriptor.request, platform))) {
|
||||
const [path, request] = [
|
||||
join(descriptor.path, platform),
|
||||
join(descriptor.request, platform),
|
||||
];
|
||||
await doExplication({path, request});
|
||||
if (path !== request) {
|
||||
resolver.addAlias(path, request);
|
||||
}
|
||||
}
|
||||
else if (await resolver.resolve(join(descriptorRequest, 'src', platform))) {
|
||||
const [path, request] = [
|
||||
join(descriptor.path, platform),
|
||||
join(descriptorRequest, 'src', platform),
|
||||
];
|
||||
await doExplication({path, request});
|
||||
if (path !== request) {
|
||||
resolver.addAlias(path, request);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
async function explicateRoot(descriptor) {
|
||||
// Walk up and find the root, if any.
|
||||
const rootDescriptor = await getRootDescriptor(descriptor);
|
||||
if (!rootDescriptor || roots[rootDescriptor.request]) {
|
||||
return;
|
||||
}
|
||||
const {path, request} = rootDescriptor;
|
||||
if (path !== request) {
|
||||
resolver.addModules(relative(FLECKS_CORE_ROOT, join(request, 'node_modules')));
|
||||
}
|
||||
roots[request] = true;
|
||||
// Import bootstrap script.
|
||||
const bootstrapPath = await resolver.resolve(join(request, 'build', 'flecks.bootstrap'));
|
||||
const bootstrap = bootstrapPath ? importer(bootstrapPath) : {};
|
||||
roots[request] = bootstrap;
|
||||
// Explicate dependcies.
|
||||
const {dependencies = []} = bootstrap;
|
||||
if (dependencies.length > 0) {
|
||||
await Promise.all(
|
||||
dependencies
|
||||
.map(createDescriptor)
|
||||
.map(explicateDescriptor),
|
||||
);
|
||||
}
|
||||
await explicateDescriptor(rootDescriptor);
|
||||
}
|
||||
await Promise.all(
|
||||
maybeAliasedPaths
|
||||
.map(createDescriptor)
|
||||
.map(explicateDescriptor),
|
||||
// Normalize maybe aliased paths into path and request.
|
||||
const normalized = await Promise.all(
|
||||
maybeAliasedPaths.map(async (maybeAliasedPath) => {
|
||||
const index = maybeAliasedPath.indexOf(':');
|
||||
return -1 === index
|
||||
? [maybeAliasedPath, maybeAliasedPath]
|
||||
: [maybeAliasedPath.slice(0, index), maybeAliasedPath.slice(index + 1)];
|
||||
}),
|
||||
);
|
||||
return {
|
||||
descriptors,
|
||||
roots,
|
||||
};
|
||||
// Add roots.
|
||||
await Promise.all(normalized.map(([path, request]) => addRoot(path, request)));
|
||||
// Add aliases and fallbacks.
|
||||
await Promise.all(
|
||||
Object.entries(roots)
|
||||
.filter(([path, {request}]) => path !== request)
|
||||
.map(async ([path, {request}]) => {
|
||||
try {
|
||||
await access(join(request, 'src'));
|
||||
resolver.addAlias(path, join(request, 'src'));
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
catch (error) {}
|
||||
resolver.addFallback(path, request);
|
||||
}),
|
||||
);
|
||||
const paths = (
|
||||
// Resolve dependent, normalized, and platform paths.
|
||||
await Promise.all(
|
||||
dependentPaths.map((path) => [path, path])
|
||||
.concat(normalized)
|
||||
.map(([path]) => path)
|
||||
.reduce((platformed, path) => (
|
||||
platformed.concat([path], platforms.map((platform) => join(path, platform)))
|
||||
), [])
|
||||
.map(async (path) => [path, await resolver.resolve(path)]),
|
||||
)
|
||||
)
|
||||
// Filter unresolved except roots.
|
||||
.filter(([path, resolved]) => resolved || roots[path])
|
||||
.map(([path]) => path)
|
||||
// Filter excluded platforms.
|
||||
.filter((path) => (
|
||||
!platforms
|
||||
.filter((platform) => platform.startsWith('!'))
|
||||
.map((platform) => platform.slice(1))
|
||||
.some((excluded) => path.endsWith(`/${excluded}`))
|
||||
));
|
||||
return {paths: [...new Set(paths)], roots};
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ exports.hooks = {
|
|||
}),
|
||||
);
|
||||
}
|
||||
if (Object.entries(flecks.compiled).length > 0) {
|
||||
if (flecks.roots.some(([path, request]) => path !== request)) {
|
||||
config.resolve.symlinks = false;
|
||||
}
|
||||
config.plugins.push(new ProcessAssets(target, flecks));
|
||||
|
|
27
packages/build/build/resolve.js
Normal file
27
packages/build/build/resolve.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
const Resolver = require('./resolver');
|
||||
|
||||
module.exports = function resolve({aliases, fallbacks}, stubs) {
|
||||
const {Module} = require('module');
|
||||
const {require: Mr} = Module.prototype;
|
||||
const resolver = new Resolver({aliases, fallbacks, useSyncFileSystemCalls: true});
|
||||
Module.prototype.require = function hackedRequire(request, options) {
|
||||
for (let i = 0; i < stubs.length; ++i) {
|
||||
if (request.startsWith(stubs[i])) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return Mr.call(this, request, options);
|
||||
}
|
||||
catch (error) {
|
||||
if (!error.message.startsWith('Cannot find module')) {
|
||||
throw error;
|
||||
}
|
||||
const resolved = resolver.resolveSync(request);
|
||||
if (!resolved) {
|
||||
throw error;
|
||||
}
|
||||
return Mr.call(this, resolved, options);
|
||||
}
|
||||
};
|
||||
};
|
|
@ -22,18 +22,29 @@ const nodeFileSystem = new CachedInputFileSystem(fs, 4000);
|
|||
|
||||
module.exports = class Resolver {
|
||||
|
||||
constructor(options) {
|
||||
constructor(options = {}) {
|
||||
const {
|
||||
modules = [join(FLECKS_CORE_ROOT, 'node_modules'), 'node_modules'],
|
||||
root = FLECKS_CORE_ROOT,
|
||||
...rest
|
||||
} = options;
|
||||
this.resolver = ResolverFactory.createResolver({
|
||||
conditionNames: ['node'],
|
||||
extensions: ['.js', '.json', '.node'],
|
||||
fileSystem: nodeFileSystem,
|
||||
modules,
|
||||
symlinks: false,
|
||||
...options,
|
||||
...rest,
|
||||
});
|
||||
this.aliases = {};
|
||||
this.fallbacks = {};
|
||||
this.modules = modules;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
addAlias(name, alias) {
|
||||
debugSilly("adding alias: '%s' -> '%s'", name, alias);
|
||||
this.aliases[name] = alias;
|
||||
new AliasPlugin(
|
||||
'raw-resolve',
|
||||
{name, onlyModule: false, alias},
|
||||
|
@ -50,6 +61,7 @@ module.exports = class Resolver {
|
|||
|
||||
addFallback(name, alias) {
|
||||
debugSilly("adding fallback: '%s' -> '%s'", name, alias);
|
||||
this.fallbacks[name] = alias;
|
||||
new AliasPlugin(
|
||||
'described-resolve',
|
||||
{name, onlyModule: false, alias},
|
||||
|
@ -59,10 +71,11 @@ module.exports = class Resolver {
|
|||
|
||||
addModules(path) {
|
||||
debugSilly("adding modules: '%s'", path);
|
||||
this.modules.push(path);
|
||||
new ModulesInHierarchicalDirectoriesPlugin(
|
||||
"raw-module",
|
||||
'raw-module',
|
||||
path,
|
||||
"module"
|
||||
'module',
|
||||
).apply(this.resolver);
|
||||
}
|
||||
|
||||
|
@ -73,7 +86,7 @@ module.exports = class Resolver {
|
|||
async resolve(request) {
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.resolver.resolve(nodeContext, FLECKS_CORE_ROOT, request, {}, (error, path) => {
|
||||
this.resolver.resolve(nodeContext, this.root, request, {}, (error, path) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
@ -91,4 +104,16 @@ module.exports = class Resolver {
|
|||
}
|
||||
}
|
||||
|
||||
resolveSync(request) {
|
||||
try {
|
||||
return this.resolver.resolveSync(nodeContext, this.root, request);
|
||||
}
|
||||
catch (error) {
|
||||
if (!this.constructor.isResolutionError(error)) {
|
||||
throw error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -41,6 +41,12 @@ exports.defaultConfig = (flecks, specializedConfig) => {
|
|||
},
|
||||
plugins: [],
|
||||
resolve: {
|
||||
alias: {},
|
||||
extensions,
|
||||
fallback: {},
|
||||
modules: [],
|
||||
},
|
||||
resolveLoader: {
|
||||
alias: {},
|
||||
extensions,
|
||||
fallback: {},
|
||||
|
|
|
@ -12,69 +12,94 @@ const {
|
|||
const root = join(FLECKS_CORE_ROOT, 'test', 'server', 'explicate');
|
||||
|
||||
function createExplication(paths, platforms) {
|
||||
const resolver = new Resolver({modules: [join(root, 'fake_node_modules')]});
|
||||
return explicate(
|
||||
const resolver = new Resolver({
|
||||
modules: [join(root, 'fake_node_modules')],
|
||||
root,
|
||||
});
|
||||
return explicate({
|
||||
paths,
|
||||
{
|
||||
platforms,
|
||||
resolver,
|
||||
root,
|
||||
importer: (request) => __non_webpack_require__(request),
|
||||
},
|
||||
);
|
||||
platforms,
|
||||
resolver,
|
||||
importer: (request) => __non_webpack_require__(request),
|
||||
});
|
||||
}
|
||||
|
||||
describe('explication', () => {
|
||||
|
||||
it('derives platforms', async () => {
|
||||
expect(Object.keys((await createExplication(['platformed'])).descriptors))
|
||||
.to.deep.equal([
|
||||
'platformed', 'platformed/server',
|
||||
]);
|
||||
expect(Object.keys((await createExplication(['server-only'])).descriptors))
|
||||
.to.deep.equal([
|
||||
'server-only/server',
|
||||
]);
|
||||
expect(await createExplication(['platformed']))
|
||||
.to.deep.include({
|
||||
paths: ['platformed', 'platformed/server'],
|
||||
});
|
||||
expect(await createExplication(['server-only']))
|
||||
.to.deep.include({
|
||||
paths: ['server-only/server'],
|
||||
});
|
||||
});
|
||||
|
||||
it('derives through bootstrap', async () => {
|
||||
expect(Object.keys((await createExplication(['real-root'])).descriptors))
|
||||
.to.deep.equal([
|
||||
'dependency', 'dependency/server',
|
||||
'real-root', 'real-root/server',
|
||||
]);
|
||||
expect(await createExplication(['real-root']))
|
||||
.to.deep.include({
|
||||
paths: [
|
||||
'dependency', 'dependency/server',
|
||||
'real-root', 'real-root/server',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('excludes platforms', async () => {
|
||||
expect(Object.keys(
|
||||
(await createExplication(
|
||||
expect(
|
||||
await createExplication(
|
||||
['platformed/client', 'dependency'],
|
||||
['server', '!client'],
|
||||
)).descriptors,
|
||||
))
|
||||
.to.deep.equal([
|
||||
'dependency', 'dependency/server',
|
||||
]);
|
||||
),
|
||||
)
|
||||
.to.deep.include({
|
||||
paths: ['dependency', 'dependency/server'],
|
||||
});
|
||||
});
|
||||
|
||||
it('explicates parents first', async () => {
|
||||
expect(Object.keys((await createExplication(['real-root/server'])).descriptors))
|
||||
.to.deep.equal([
|
||||
'dependency', 'dependency/server',
|
||||
'real-root', 'real-root/server',
|
||||
]);
|
||||
expect(await createExplication(['real-root/server']))
|
||||
.to.deep.include({
|
||||
paths: [
|
||||
'dependency', 'dependency/server',
|
||||
'real-root', 'real-root/server',
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('explicates only bootstrapped', async () => {
|
||||
expect(Object.keys((await createExplication(['only-bootstrapped'])).descriptors))
|
||||
.to.deep.equal([
|
||||
'only-bootstrapped',
|
||||
]);
|
||||
expect(await createExplication(['only-bootstrapped']))
|
||||
.to.deep.include({
|
||||
paths: ['only-bootstrapped'],
|
||||
});
|
||||
});
|
||||
|
||||
it('explicates root with src', async () => {
|
||||
expect(await createExplication(['src-root:./src-root']))
|
||||
.to.deep.include({
|
||||
paths: ['src-root', 'src-root/server'],
|
||||
});
|
||||
});
|
||||
|
||||
it('skips nonexistent', async () => {
|
||||
expect(await createExplication(['real-root/nonexistent']))
|
||||
.to.deep.equal({descriptors: {}, roots: {}});
|
||||
.to.deep.equal({paths: [], roots: {}});
|
||||
});
|
||||
|
||||
it('includes modules', async () => {
|
||||
expect(await createExplication(['modules-root:./modules-root', 'foo']))
|
||||
.to.deep.include({
|
||||
paths: ['modules-root', 'foo'],
|
||||
});
|
||||
});
|
||||
|
||||
it('explicates aliased platforms', async () => {
|
||||
expect(await createExplication(['aliased-platforms:./aliased-platforms']))
|
||||
.to.deep.include({
|
||||
paths: ['aliased-platforms', 'aliased-platforms/server'],
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -8,7 +8,7 @@ module.exports = async (env, argv) => {
|
|||
config.plugins.push(new ProcessAssets('fleck', flecks));
|
||||
// Small hack because internals.
|
||||
flecks.hooks['@flecks/build.processAssets'] = [{
|
||||
hook: '@flecks/build',
|
||||
fleck: '@flecks/build',
|
||||
fn: (target, assets, compilation) => processFleckAssets(assets, compilation),
|
||||
}];
|
||||
return config;
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
module.exports = function stub(stubs) {
|
||||
if (0 === stubs.length) {
|
||||
return;
|
||||
}
|
||||
const {Module} = require('module');
|
||||
const {require: Mr} = Module.prototype;
|
||||
Module.prototype.require = function hackedRequire(request, options) {
|
||||
for (let i = 0; i < stubs.length; ++i) {
|
||||
if (request.match(stubs[i])) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return Mr.call(this, request, options);
|
||||
};
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"eslint.workingDirectories": [{"pattern": "./packages/*"}],
|
||||
"eslint.options": {"overrideConfigFile": "./node_modules/@flecks/build/build/eslint.config.js"},
|
||||
"eslint.options": {"overrideConfigFile": "./node_modules/@flecks/build/build/eslint.config.js"}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
"build:only": "flecks build",
|
||||
"debug": "DEBUG=@flecks/* npm run dev",
|
||||
"dev": "npm run -- build:only -dh",
|
||||
"postinstall": "patch-package",
|
||||
"repl": "npx flecks repl --rlwrap",
|
||||
"start": "DEBUG=@flecks/*,-*:silly npm run dev"
|
||||
},
|
||||
|
|
|
@ -108,13 +108,13 @@ exports.parseSource = async (path, source) => {
|
|||
return exports.parseNormalSource(path, source);
|
||||
};
|
||||
|
||||
exports.parseFleckRoot = async (path, root) => (
|
||||
exports.parseFleckRoot = async (request) => (
|
||||
Promise.all(
|
||||
(await Promise.all([
|
||||
...await glob(join(root, 'src', '**', '*.js')),
|
||||
...await glob(join(root, 'build', '**', '*.js')),
|
||||
...await glob(join(request, 'src', '**', '*.js')),
|
||||
...await glob(join(request, 'build', '**', '*.js')),
|
||||
]))
|
||||
.map((filename) => [relative(root, filename), filename])
|
||||
.map((filename) => [relative(request, filename), filename])
|
||||
.map(async ([path, filename]) => {
|
||||
const buffer = await readFile(filename);
|
||||
return [path, await exports.parseSource(path, buffer.toString('utf8'))];
|
||||
|
@ -124,7 +124,7 @@ exports.parseFleckRoot = async (path, root) => (
|
|||
|
||||
exports.parseFlecks = async (flecks) => (
|
||||
Promise.all(
|
||||
Object.entries(flecks.roots)
|
||||
.map(async ([path, {root}]) => [path, await exports.parseFleckRoot(path, root)]),
|
||||
flecks.roots
|
||||
.map(async ([path, request]) => [path, await exports.parseFleckRoot(request)]),
|
||||
)
|
||||
);
|
||||
|
|
|
@ -77,7 +77,7 @@ module.exports = (program, flecks) => {
|
|||
});
|
||||
});
|
||||
};
|
||||
require('@flecks/core/build/stub')(flecks.stubs);
|
||||
require('@flecks/build/build/resolve')(flecks.resolver, flecks.stubs);
|
||||
if (!watch) {
|
||||
await new Promise((resolve, reject) => {
|
||||
child.on('exit', (code) => {
|
||||
|
|
|
@ -2,6 +2,7 @@ const flecksConfigFn = require('@flecks/build/build/fleck.webpack.config');
|
|||
|
||||
module.exports = async (env, argv, flecks) => {
|
||||
const config = await flecksConfigFn(env, argv, flecks);
|
||||
config.resolve.modules.push('node_modules');
|
||||
config.stats = flecks.get('@flecks/fleck.stats');
|
||||
return config;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
const {externals} = require('@flecks/build/server');
|
||||
|
||||
const D = require('@flecks/core/build/debug');
|
||||
|
||||
const debug = D('@flecks/server/build/runtime');
|
||||
|
||||
module.exports = async (config, env, argv, flecks) => {
|
||||
const runtimePath = await flecks.resolver.resolve('@flecks/server/runtime');
|
||||
// Inject flecks configuration.
|
||||
|
@ -10,6 +14,10 @@ module.exports = async (config, env, argv, flecks) => {
|
|||
.filter(([, resolved]) => resolved)
|
||||
.map(([path]) => path);
|
||||
const runtime = {
|
||||
resolver: JSON.stringify({
|
||||
aliases: flecks.resolver.aliases,
|
||||
fallbacks: flecks.resolver.fallbacks,
|
||||
}),
|
||||
config: JSON.stringify(flecks.config),
|
||||
loadFlecks: [
|
||||
'async () => (',
|
||||
|
@ -98,14 +106,19 @@ module.exports = async (config, env, argv, flecks) => {
|
|||
/^@babel\/runtime\/helpers\/esm/,
|
||||
];
|
||||
config.resolve.alias['@flecks/server/runtime$'] = runtimePath;
|
||||
const nodeExternalsConfig = {
|
||||
allowlist,
|
||||
};
|
||||
await flecks.runtimeCompiler('server', config, nodeExternalsConfig);
|
||||
Object.entries(flecks.resolver.aliases).forEach(([path, request]) => {
|
||||
debug('server runtime de-externalized %s, alias: %s', path, request);
|
||||
allowlist.push(new RegExp(`^${path}`));
|
||||
});
|
||||
// Stubs.
|
||||
flecks.stubs.forEach((stub) => {
|
||||
config.resolve.alias[stub] = false;
|
||||
});
|
||||
await flecks.runtimeCompiler('server', config);
|
||||
// Rewrite to signals for HMR.
|
||||
if ('production' !== argv.mode) {
|
||||
allowlist.push(/^webpack\/hot\/signal/);
|
||||
}
|
||||
// Externalize the rest.
|
||||
config.externals = externals(nodeExternalsConfig);
|
||||
config.externals = externals({allowlist});
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ module.exports = async (env, argv, flecks) => {
|
|||
config.entry.index.push('@flecks/server/entry');
|
||||
// Augment the application-starting configuration.
|
||||
if (isStarting) {
|
||||
if (Object.entries(flecks.compiled).length > 0) {
|
||||
if (flecks.roots.some(([path, request]) => path !== request)) {
|
||||
nodeEnv.NODE_PRESERVE_SYMLINKS = 1;
|
||||
}
|
||||
config.plugins.push(
|
||||
|
|
|
@ -8,7 +8,7 @@ const {version} = require('../package.json');
|
|||
|
||||
(async () => {
|
||||
const runtime = await __non_webpack_require__('@flecks/server/runtime');
|
||||
const {loadFlecks, stubs} = runtime;
|
||||
const {loadFlecks, resolver, stubs} = runtime;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`flecks server v${version}`);
|
||||
try {
|
||||
|
@ -24,7 +24,7 @@ const {version} = require('../package.json');
|
|||
const unserializedStubs = stubs.map((stub) => (Array.isArray(stub) ? new RegExp(...stub) : stub));
|
||||
if (unserializedStubs.length > 0) {
|
||||
debug('stubbing with %O', unserializedStubs);
|
||||
__non_webpack_require__('@flecks/core/build/stub')(unserializedStubs);
|
||||
__non_webpack_require__('@flecks/build/build/resolve')(resolver, unserializedStubs);
|
||||
}
|
||||
global.flecks = await Flecks.from({...runtime, flecks: await loadFlecks()});
|
||||
try {
|
||||
|
|
|
@ -315,9 +315,3 @@ exports.hooks = {
|
|||
return JSON.stringify(config);
|
||||
},
|
||||
};
|
||||
|
||||
exports.stubs = {
|
||||
server: [
|
||||
/\.(c|s[ac])ss$/,
|
||||
],
|
||||
};
|
||||
|
|
|
@ -22,16 +22,12 @@ module.exports = async (config, env, argv, flecks) => {
|
|||
Object.keys(flecks.flecks)
|
||||
.map(async (fleck) => {
|
||||
// No root? How to infer?
|
||||
const [root] = Object.entries(flecks.roots)
|
||||
.find(([root]) => fleck.startsWith(root)) || [];
|
||||
const [root, request] = 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))
|
||||
) {
|
||||
if (root !== request) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
|
@ -96,12 +92,7 @@ module.exports = async (config, env, argv, flecks) => {
|
|||
config.resolve.alias['@flecks/web/runtime$'] = runtime;
|
||||
// Stubs.
|
||||
buildFlecks.stubs.forEach((stub) => {
|
||||
config.module.rules.push(
|
||||
{
|
||||
test: stub,
|
||||
use: 'null-loader',
|
||||
},
|
||||
);
|
||||
config.resolve.alias[stub] = false;
|
||||
});
|
||||
await buildFlecks.runtimeCompiler('web', config);
|
||||
// Styles.
|
||||
|
@ -109,26 +100,18 @@ module.exports = async (config, env, argv, flecks) => {
|
|||
// Tests.
|
||||
if (!isProduction) {
|
||||
const testEntries = (await Promise.all(
|
||||
Object.entries(buildFlecks.roots)
|
||||
.map(async ([parent, {request}]) => {
|
||||
buildFlecks.roots
|
||||
.map(async ([root, 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 rootTests = await glob(join(request, 'test', '*.js'));
|
||||
tests.push(...rootTests.map((test) => test.replace(request, root)));
|
||||
const platformTests = await Promise.all(
|
||||
buildFlecks.platforms.map((platform) => (
|
||||
glob(join(resolved, 'test', 'platforms', platform, '*.js'))
|
||||
glob(join(request, 'test', platform, '*.js'))
|
||||
)),
|
||||
);
|
||||
tests.push(
|
||||
...platformTests
|
||||
.flat()
|
||||
.map((test) => test.replace(resolved, parent)),
|
||||
);
|
||||
return [parent, tests];
|
||||
tests.push(...platformTests.flat().map((test) => test.replace(request, root)));
|
||||
return [root, tests];
|
||||
}),
|
||||
))
|
||||
.filter(([, tests]) => tests.length > 0);
|
||||
|
|
|
@ -35,6 +35,7 @@ module.exports = async (env, argv, flecks) => {
|
|||
stream: false,
|
||||
util: require.resolve('util'),
|
||||
},
|
||||
modules: flecks.resolver.modules,
|
||||
},
|
||||
stats: {
|
||||
warningsFilter: [
|
||||
|
|
Loading…
Reference in New Issue
Block a user