305 lines
7.4 KiB
TypeScript
305 lines
7.4 KiB
TypeScript
import type { NuxtOptions } from '@nuxt/schema';
|
|
import {
|
|
CreateDependencies,
|
|
CreateNodes,
|
|
CreateNodesContext,
|
|
createNodesFromFiles,
|
|
CreateNodesV2,
|
|
detectPackageManager,
|
|
getPackageManagerCommand,
|
|
readJsonFile,
|
|
TargetConfiguration,
|
|
workspaceRoot,
|
|
writeJsonFile,
|
|
} from '@nx/devkit';
|
|
import { loadConfigFile } from '@nx/devkit/src/utils/config-utils';
|
|
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
|
|
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
|
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
|
import { getLockFileName } from '@nx/js';
|
|
import { dirname, isAbsolute, join, relative } from 'path';
|
|
import { existsSync, readdirSync } from 'fs';
|
|
import { loadNuxtKitDynamicImport } from '../utils/executor-utils';
|
|
import { addBuildAndWatchDepsTargets } from '@nx/js/src/plugins/typescript/util';
|
|
|
|
const cachePath = join(workspaceDataDirectory, 'nuxt.hash');
|
|
const targetsCache = readTargetsCache();
|
|
|
|
const pmc = getPackageManagerCommand();
|
|
|
|
function readTargetsCache(): Record<
|
|
string,
|
|
Record<string, TargetConfiguration>
|
|
> {
|
|
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
|
|
}
|
|
|
|
function writeTargetsToCache() {
|
|
const oldCache = readTargetsCache();
|
|
writeJsonFile(cachePath, {
|
|
...oldCache,
|
|
...targetsCache,
|
|
});
|
|
}
|
|
|
|
export interface NuxtPluginOptions {
|
|
buildTargetName?: string;
|
|
serveTargetName?: string;
|
|
serveStaticTargetName?: string;
|
|
buildStaticTargetName?: string;
|
|
buildDepsTargetName?: string;
|
|
watchDepsTargetName?: string;
|
|
}
|
|
|
|
export const createNodesV2: CreateNodesV2<NuxtPluginOptions> = [
|
|
'**/nuxt.config.{js,ts,mjs,mts,cjs,cts}',
|
|
async (files, options, context) => {
|
|
//TODO(@nrwl/nx-vue-reviewers): This should batch hashing like our other plugins.
|
|
const result = await createNodesFromFiles(
|
|
createNodes[1],
|
|
files,
|
|
options,
|
|
context
|
|
);
|
|
writeTargetsToCache();
|
|
return result;
|
|
},
|
|
];
|
|
|
|
export const createNodes: CreateNodes<NuxtPluginOptions> = [
|
|
'**/nuxt.config.{js,ts,mjs,mts,cjs,cts}',
|
|
async (configFilePath, options, context) => {
|
|
const projectRoot = dirname(configFilePath);
|
|
// Do not create a project if package.json and project.json isn't there.
|
|
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
|
|
if (
|
|
!siblingFiles.includes('package.json') &&
|
|
!siblingFiles.includes('project.json')
|
|
) {
|
|
return {};
|
|
}
|
|
|
|
options = normalizeOptions(options);
|
|
|
|
const hash = await calculateHashForCreateNodes(
|
|
projectRoot,
|
|
options,
|
|
context,
|
|
[getLockFileName(detectPackageManager(context.workspaceRoot))]
|
|
);
|
|
targetsCache[hash] ??= await buildNuxtTargets(
|
|
configFilePath,
|
|
projectRoot,
|
|
options,
|
|
context
|
|
);
|
|
|
|
return {
|
|
projects: {
|
|
[projectRoot]: {
|
|
root: projectRoot,
|
|
targets: targetsCache[hash],
|
|
},
|
|
},
|
|
};
|
|
},
|
|
];
|
|
|
|
async function buildNuxtTargets(
|
|
configFilePath: string,
|
|
projectRoot: string,
|
|
options: NuxtPluginOptions,
|
|
context: CreateNodesContext
|
|
) {
|
|
const nuxtConfig: {
|
|
buildDir: string;
|
|
} = await getInfoFromNuxtConfig(configFilePath, context, projectRoot);
|
|
|
|
const { buildOutputs } = getOutputs(nuxtConfig, projectRoot);
|
|
|
|
const namedInputs = getNamedInputs(projectRoot, context);
|
|
|
|
const targets: Record<string, TargetConfiguration> = {};
|
|
|
|
targets[options.buildTargetName] = buildTarget(
|
|
options.buildTargetName,
|
|
namedInputs,
|
|
buildOutputs,
|
|
projectRoot
|
|
);
|
|
|
|
targets[options.serveTargetName] = serveTarget(projectRoot);
|
|
|
|
targets[options.serveStaticTargetName] = serveStaticTarget(options);
|
|
|
|
targets[options.buildStaticTargetName] = buildStaticTarget(
|
|
options.buildStaticTargetName,
|
|
namedInputs,
|
|
buildOutputs,
|
|
projectRoot
|
|
);
|
|
|
|
addBuildAndWatchDepsTargets(
|
|
context.workspaceRoot,
|
|
projectRoot,
|
|
targets,
|
|
options,
|
|
pmc
|
|
);
|
|
|
|
return targets;
|
|
}
|
|
|
|
function buildTarget(
|
|
buildTargetName: string,
|
|
namedInputs: {
|
|
[inputName: string]: any[];
|
|
},
|
|
buildOutputs: string[],
|
|
projectRoot: string
|
|
) {
|
|
return {
|
|
command: `nuxt build`,
|
|
options: { cwd: projectRoot },
|
|
cache: true,
|
|
dependsOn: [`^${buildTargetName}`],
|
|
inputs: [
|
|
...('production' in namedInputs
|
|
? ['production', '^production']
|
|
: ['default', '^default']),
|
|
|
|
{
|
|
externalDependencies: ['nuxt'],
|
|
},
|
|
],
|
|
outputs: buildOutputs,
|
|
};
|
|
}
|
|
|
|
function serveTarget(projectRoot: string) {
|
|
const targetConfig: TargetConfiguration = {
|
|
command: `nuxt dev`,
|
|
options: {
|
|
cwd: projectRoot,
|
|
},
|
|
};
|
|
|
|
return targetConfig;
|
|
}
|
|
|
|
function serveStaticTarget(options: NuxtPluginOptions) {
|
|
const targetConfig: TargetConfiguration = {
|
|
executor: '@nx/web:file-server',
|
|
options: {
|
|
buildTarget: `${options.buildStaticTargetName}`,
|
|
staticFilePath: '{projectRoot}/dist',
|
|
port: 4200,
|
|
// Routes are found correctly with serve-static
|
|
spa: false,
|
|
},
|
|
};
|
|
|
|
return targetConfig;
|
|
}
|
|
|
|
function buildStaticTarget(
|
|
buildStaticTargetName: string,
|
|
namedInputs: {
|
|
[inputName: string]: any[];
|
|
},
|
|
buildOutputs: string[],
|
|
projectRoot: string
|
|
) {
|
|
const targetConfig: TargetConfiguration = {
|
|
command: `nuxt build --prerender`,
|
|
options: { cwd: projectRoot },
|
|
cache: true,
|
|
dependsOn: [`^${buildStaticTargetName}`],
|
|
inputs: [
|
|
...('production' in namedInputs
|
|
? ['production', '^production']
|
|
: ['default', '^default']),
|
|
|
|
{
|
|
externalDependencies: ['nuxt'],
|
|
},
|
|
],
|
|
outputs: buildOutputs,
|
|
};
|
|
return targetConfig;
|
|
}
|
|
|
|
async function getInfoFromNuxtConfig(
|
|
configFilePath: string,
|
|
context: CreateNodesContext,
|
|
projectRoot: string
|
|
): Promise<{
|
|
buildDir: string;
|
|
}> {
|
|
let config: NuxtOptions;
|
|
if (process.env.NX_ISOLATE_PLUGINS !== 'false') {
|
|
config = await (
|
|
await loadNuxtKitDynamicImport()
|
|
).loadNuxtConfig({
|
|
configFile: configFilePath,
|
|
});
|
|
} else {
|
|
config = await loadConfigFile(join(context.workspaceRoot, configFilePath));
|
|
}
|
|
return {
|
|
buildDir:
|
|
config?.buildDir ??
|
|
// Match .nuxt default build dir from '@nuxt/schema'
|
|
// See: https://github.com/nuxt/nuxt/blob/871404ae5673425aeedde82f123ea58aa7c6facf/packages/schema/src/config/common.ts#L117-L119
|
|
'.nuxt',
|
|
};
|
|
}
|
|
|
|
function getOutputs(
|
|
nuxtConfig: { buildDir: string },
|
|
projectRoot: string
|
|
): {
|
|
buildOutputs: string[];
|
|
} {
|
|
const buildOutputPath = normalizeOutputPath(
|
|
nuxtConfig?.buildDir,
|
|
projectRoot
|
|
);
|
|
|
|
return {
|
|
buildOutputs: [buildOutputPath, '{projectRoot}/.output'],
|
|
};
|
|
}
|
|
|
|
function normalizeOutputPath(
|
|
outputPath: string | undefined,
|
|
projectRoot: string
|
|
): string {
|
|
if (!outputPath) {
|
|
if (projectRoot === '.') {
|
|
return `{projectRoot}`;
|
|
} else {
|
|
return `{workspaceRoot}/{projectRoot}`;
|
|
}
|
|
} else {
|
|
if (isAbsolute(outputPath)) {
|
|
return `{workspaceRoot}/${relative(workspaceRoot, outputPath)}`;
|
|
} else {
|
|
if (outputPath.startsWith('..')) {
|
|
return join('{workspaceRoot}', join(projectRoot, outputPath));
|
|
} else {
|
|
return join('{projectRoot}', outputPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function normalizeOptions(options: NuxtPluginOptions): NuxtPluginOptions {
|
|
options ??= {};
|
|
options.buildTargetName ??= 'build';
|
|
options.serveTargetName ??= 'serve';
|
|
options.serveStaticTargetName ??= 'serve-static';
|
|
options.buildStaticTargetName ??= 'build-static';
|
|
return options;
|
|
}
|