Open joeflateau opened 2 years ago
Yep, I have done this for my Remix blog migration. Have it on my list of improvements 🙂
awesome! thanks
Meanwhile if you want, you can follow an approach of having a local workspace plugin like I talked about here: https://youtu.be/ptpEBhHwl6Q?t=791
Note, in Nx 13.10 (soon to be released) this whole local plugin setup is going to be even better, not needing any type of precompilation. But more soon :)
And then create an executor like
import { BuildExecutorSchema } from './schema';
import { createPackageJson } from '@nrwl/workspace/src/utilities/create-package-json';
import { readCachedProjectGraph } from '@nrwl/workspace/src/core/project-graph';
import {
ExecutorContext,
writeJsonFile,
} from '@nrwl/devkit';
import { execSync } from 'child_process';
import { ensureDirSync } from 'fs-extra';
export default async function runExecutor(
options: BuildExecutorSchema,
context: ExecutorContext
) {
ensureDirSync(options.outputPath);
const depGraph = readCachedProjectGraph();
const projectSrcRoot =
context.workspace.projects[context.projectName].sourceRoot;
const packageJson = createPackageJson(context.projectName, depGraph, {
root: context.root,
projectRoot: projectSrcRoot,
});
// delete postinstall scripts as you don't want to have it in your Docker container
delete packageJson?.scripts?.postinstall;
// do other stuff specific to your setup
// write the package.json to your output path (or wherever you want) where your Docker build will package it up
writeJsonFile(`${options.outputPath}/package.json`, packageJson);
return {
success: true,
};
}
Note the above is just a rough copy of my local executor I've hacked quickly together. Once I have something more refined that works in a more generic way I'll integrate it into the Remix plugin or write a blog post on how to do it.
oh cool, i'm going to use this as an opportunity to figure out how these local plugins/tools/executors work
this worked for me, thanks so much!
import { ExecutorContext, writeJsonFile } from '@nrwl/devkit';
import { readCachedProjectGraph } from '@nrwl/workspace/src/core/project-graph';
import runScript from '@nrwl/workspace/src/executors/run-script/run-script.impl';
import { createPackageJson } from '@nrwl/workspace/src/utilities/create-package-json';
import { copy, ensureDir } from 'fs-extra';
import { BuildExecutorSchema } from './schema';
export default async function runExecutor(
options: BuildExecutorSchema,
context: ExecutorContext
) {
ensureDir(options.outputPath);
const depGraph = readCachedProjectGraph();
const projectSrcRoot =
context.workspace.projects[context.projectName].sourceRoot;
const packageJson = createPackageJson(context.projectName, depGraph, {
root: context.root,
projectRoot: projectSrcRoot,
});
// delete postinstall scripts as you don't want to have it in your Docker container
delete packageJson?.scripts?.postinstall;
// do other stuff specific to your setup
await runScript({ script: 'build' }, context);
for (const subdir of ['build', 'public']) {
await copy(
`${projectSrcRoot}/${subdir}`,
`${options.outputPath}/${subdir}`
);
}
// write the package.json to your output path (or wherever you want) where your Docker build will package it up
writeJsonFile(`${options.outputPath}/package.json`, packageJson);
return {
success: true,
};
}
@joeflateau can you share your executors folder for your working solution?
+1 - currently looking to deploy a Remix app with nx.
I used the above code to create my own executor which just creates a package.json based on the dep-graph with the possibility to exclude some dependencies based on a glob pattern (see excludedFilesGlob
). In my case I have MSW setup in a 'mocks' folder, but don't want to include the dependencies from that code in the resulting package.json.
import path from 'path';
import { ExecutorContext, FileData, ProjectConfiguration, ProjectGraph, readCachedProjectGraph, writeJsonFile } from '@nrwl/devkit';
import { createPackageJson } from '@nrwl/workspace/src/utilities/create-package-json';
import { ensureDir } from 'fs-extra';
import partition from 'lodash/partition';
import minimatch from 'minimatch';
import { CreatePackageJsonExecutorSchema } from './schema';
export default async function (options: CreatePackageJsonExecutorSchema, context: ExecutorContext) {
ensureDir(options.outputPath);
const projectRoot = context.workspace.projects[context.projectName].sourceRoot;
const depGraph = readCachedProjectGraph();
if (options.excludedFilesGlob !== undefined) {
filterDependenciesByExcludedFilesGlob(depGraph, context.projectName, projectRoot, options.excludedFilesGlob);
}
const packageJson = createPackageJson(context.projectName, depGraph, {
root: context.root,
projectRoot: projectRoot,
});
writeJsonFile(`${options.outputPath}/package.json`, packageJson);
return { success: true };
}
function filterDependenciesByExcludedFilesGlob(
depGraph: ProjectGraph<ProjectConfiguration>,
projectName: string,
projectRoot: string,
excludedFilesGlob: string
) {
const projectNode = depGraph.nodes[projectName];
const [excludedFiles, includedFiles] = partition(projectNode.data.files, (f) => minimatch(f.file, path.join(projectRoot, excludedFilesGlob)));
const possibleDepsToRemove = collectDepsFromFilesAndDedupe(excludedFiles);
const depsToRemove = possibleDepsToRemove.filter((dep) => depNotUsedInOtherFiles(includedFiles, dep));
depGraph.dependencies[projectName] = depGraph.dependencies[projectName].filter((dep) => !depsToRemove.includes(dep.target));
}
function depNotUsedInOtherFiles(includedFiles: FileData[], dep: string): unknown {
return !includedFiles.some((file) => (file.deps ?? []).includes(dep));
}
function collectDepsFromFilesAndDedupe(excludedFiles: FileData[]) {
const allDeps = excludedFiles.reduce((acc, curr) => {
(curr.deps ?? []).forEach((dep) => acc.add(dep));
return acc;
}, new Set<string>());
const dedupedDeps = Array.from(allDeps);
return dedupedDeps;
}
The schema.json
looks like this:
{
"$schema": "http://json-schema.org/schema",
"version": 2,
"cli": "nx",
"title": "CreatePackageJson executor",
"description": "",
"type": "object",
"properties": {
"outputPath": {
"type": "string",
"description": "Path where the output should be placed",
"$default": {
"$source": "argv",
"index": 0
}
},
"excludedFilesGlob": {
"type": "string",
"description": "When provided, strips the dependencies that are only used in files matching this glob"
}
},
"required": ["outputPath"]
}
And the accompanying schema.d.ts
:
export interface CreatePackageJsonExecutorSchema {
outputPath: string;
excludedFilesGlob?: string;
}
This is how you would configure the executor for a project/app in the project.json
under targets
:
"create-package-json": {
"executor": "@scope/custom-plugin:create-package-json",
"options": {
"outputPath": "apps/my-app/build",
"excludedFilesGlob": "mocks/**/*.*"
}
}
This is all that's left in the package.json under /apps/my-app
:
{
"private": true,
"name": "my-app",
"scripts": {
"start": "npx remix-serve build"
},
"dependencies": {
"@remix-run/serve": "<placeholder>"
},
"engines": {
"node": ">=14"
}
}
Note that <placeholder>
is replaced by the version from the root package.json in the resulting package.json.
Hi,
I am trying to follow the generator example proposed here but with the new @nx/devkit
I am not able to find the function createPackageJson()
.
Where I can find it?
It should be exported from @nx/devkit
now, but on a sidenote this generator does not work anymore with the latest versions. The inner logic of the graph changed and the dependencies are not exposed as before. So it's no longer possible to strip away packages that are only used in the specified excludedFilesGlob
.
Thanks for the answer, I checked here and it is not exported https://nx.dev/packages/devkit/documents/nx_devkit
maybe they changed the function name? I do not see anything similar.
Thanks for the answer, I checked here and it is not exported nx.dev/packages/devkit/documents/nx_devkit
maybe they changed the function name? I do not see anything similar.
I see the @nx/js
package exports a createPackageJson
function. But I don't know if this is part of the official public API.
how can we opt out of the package.json included with remix apps built with nx? those same deps are automagically included with root package.json. i can't find anywhere to tell the app to use the root package.json if i delete its generated one...
It would be nice if there was a remix builder that supported generatePackageJson to be able to containerize a remix app with the appropriate dependencies. Currently building a remix project runs remix build but does not generate a package.json with only the remix app's deps.