feat(web): add a generator to add @nrwl/web:file-server target (#15434)
This commit is contained in:
parent
47fd3a2d54
commit
3a4b108dd8
@ -6285,6 +6285,14 @@
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "static-config",
|
||||
"path": "/packages/web/generators/static-config",
|
||||
"name": "static-config",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
|
||||
@ -2788,6 +2788,15 @@
|
||||
"originalFilePath": "/packages/web/src/generators/application/schema.json",
|
||||
"path": "/packages/web/generators/application",
|
||||
"type": "generator"
|
||||
},
|
||||
"/packages/web/generators/static-config": {
|
||||
"description": "Add a new static-serve target to a project.",
|
||||
"file": "generated/packages/web/generators/static-config.json",
|
||||
"hidden": false,
|
||||
"name": "static-config",
|
||||
"originalFilePath": "/packages/web/src/generators/static-serve/schema.json",
|
||||
"path": "/packages/web/generators/static-config",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/packages/web"
|
||||
|
||||
@ -2756,6 +2756,15 @@
|
||||
"originalFilePath": "/packages/web/src/generators/application/schema.json",
|
||||
"path": "web/generators/application",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Add a new static-serve target to a project.",
|
||||
"file": "generated/packages/web/generators/static-config.json",
|
||||
"hidden": false,
|
||||
"name": "static-config",
|
||||
"originalFilePath": "/packages/web/src/generators/static-serve/schema.json",
|
||||
"path": "web/generators/static-config",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
|
||||
35
docs/generated/packages/web/generators/static-config.json
Normal file
35
docs/generated/packages/web/generators/static-config.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "static-config",
|
||||
"factory": "./src/generators/static-serve/static-serve-configuration",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxWebStaticServe",
|
||||
"cli": "nx",
|
||||
"title": "Static Serve Configuration",
|
||||
"description": "Add a new serve target to serve a build apps static files. This allows for faster serving of the static build files by reusing the case. Helpful when reserving the app over and over again like in e2e tests.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "Name of the build target to serve"
|
||||
},
|
||||
"outputPath": {
|
||||
"type": "string",
|
||||
"description": "Path to the directory of the built files. This is only needed if buildTarget doesn't specify an outputPath executor option."
|
||||
},
|
||||
"targetName": {
|
||||
"type": "string",
|
||||
"description": "Name of the serve target to add. Defaults to 'serve-static'.",
|
||||
"default": "serve-static"
|
||||
}
|
||||
},
|
||||
"required": ["buildTarget"],
|
||||
"presets": []
|
||||
},
|
||||
"description": "Add a new static-serve target to a project.",
|
||||
"implementation": "/packages/web/src/generators/static-serve/static-serve-configuration.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/web/src/generators/static-serve/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -10,10 +10,12 @@ import {
|
||||
} from '@nrwl/e2e/utils';
|
||||
|
||||
describe('file-server', () => {
|
||||
afterEach(() => cleanupProject());
|
||||
beforeAll(() => {
|
||||
newProject({ name: uniq('fileserver') });
|
||||
});
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
it('should serve folder of files', async () => {
|
||||
newProject({ name: uniq('fileserver') });
|
||||
const appName = uniq('app');
|
||||
const port = 4301;
|
||||
|
||||
@ -37,4 +39,46 @@ describe('file-server', () => {
|
||||
// ignore
|
||||
}
|
||||
}, 300_000);
|
||||
|
||||
it('should setup and serve static files from app', async () => {
|
||||
const ngAppName = uniq('ng-app');
|
||||
const reactAppName = uniq('react-app');
|
||||
|
||||
runCLI(`generate @nrwl/angular:app ${ngAppName} --no-interactive`);
|
||||
runCLI(`generate @nrwl/react:app ${reactAppName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/web:static-config --buildTarget=${ngAppName}:build --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nrwl/web:static-config --buildTarget=${reactAppName}:build --targetName=custom-serve-static --no-interactive`
|
||||
);
|
||||
|
||||
const ngServe = await runCommandUntil(
|
||||
`serve-static ${ngAppName}`,
|
||||
(output) => {
|
||||
return output.indexOf('localhost:4200') > -1;
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await promisifiedTreeKill(ngServe.pid, 'SIGKILL');
|
||||
await killPorts(4200);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const reactServe = await runCommandUntil(
|
||||
`custom-serve-static ${reactAppName}`,
|
||||
(output) => {
|
||||
return output.indexOf('localhost:4200') > -1;
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await promisifiedTreeKill(reactServe.pid, 'SIGKILL');
|
||||
await killPorts(4200);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, 300_000);
|
||||
});
|
||||
|
||||
@ -15,6 +15,11 @@
|
||||
"aliases": ["app"],
|
||||
"x-type": "application",
|
||||
"description": "Create an web application."
|
||||
},
|
||||
"static-config": {
|
||||
"factory": "./src/generators/static-serve/static-serve-configuration",
|
||||
"schema": "./src/generators/static-serve/schema.json",
|
||||
"description": "Add a new static-serve target to a project."
|
||||
}
|
||||
},
|
||||
"schematics": {
|
||||
@ -30,6 +35,11 @@
|
||||
"aliases": ["app"],
|
||||
"x-type": "application",
|
||||
"description": "Create an web application."
|
||||
},
|
||||
"static-config": {
|
||||
"factory": "./src/generators/static-serve/static-serve-configuration#compat",
|
||||
"schema": "./src/generators/static-serve/schema.json",
|
||||
"description": "Add a new static-serve target to a project."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
packages/web/src/generators/static-serve/schema.json
Normal file
24
packages/web/src/generators/static-serve/schema.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxWebStaticServe",
|
||||
"cli": "nx",
|
||||
"title": "Static Serve Configuration",
|
||||
"description": "Add a new serve target to serve a build apps static files. This allows for faster serving of the static build files by reusing the case. Helpful when reserving the app over and over again like in e2e tests.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"buildTarget": {
|
||||
"type": "string",
|
||||
"description": "Name of the build target to serve"
|
||||
},
|
||||
"outputPath": {
|
||||
"type": "string",
|
||||
"description": "Path to the directory of the built files. This is only needed if buildTarget doesn't specify an outputPath executor option."
|
||||
},
|
||||
"targetName": {
|
||||
"type": "string",
|
||||
"description": "Name of the serve target to add. Defaults to 'serve-static'.",
|
||||
"default": "serve-static"
|
||||
}
|
||||
},
|
||||
"required": ["buildTarget"]
|
||||
}
|
||||
@ -0,0 +1,208 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
import { webStaticServeGenerator } from './static-serve-configuration';
|
||||
|
||||
describe('Static serve configuration generator', () => {
|
||||
let tree: Tree;
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
});
|
||||
|
||||
it('should add a `serve-static` target to the project', () => {
|
||||
addReactConfig(tree, 'react-app');
|
||||
addAngularConfig(tree, 'angular-app');
|
||||
addStorybookConfig(tree, 'storybook');
|
||||
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: 'react-app:build',
|
||||
});
|
||||
|
||||
expect(readProjectConfiguration(tree, 'react-app').targets['serve-static'])
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"executor": "@nrwl/web:file-server",
|
||||
"options": Object {
|
||||
"buildTarget": "react-app:build",
|
||||
},
|
||||
}
|
||||
`);
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: 'angular-app:build',
|
||||
});
|
||||
|
||||
expect(
|
||||
readProjectConfiguration(tree, 'angular-app').targets['serve-static']
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"executor": "@nrwl/web:file-server",
|
||||
"options": Object {
|
||||
"buildTarget": "angular-app:build",
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: 'storybook:build-storybook',
|
||||
});
|
||||
expect(readProjectConfiguration(tree, 'storybook').targets['serve-static'])
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"executor": "@nrwl/web:file-server",
|
||||
"options": Object {
|
||||
"buildTarget": "storybook:build-storybook",
|
||||
"staticFilePath": "dist/apps/storybook/storybook",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should support custom target name', () => {
|
||||
addReactConfig(tree, 'react-app');
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: 'react-app:build',
|
||||
targetName: 'serve-static-custom',
|
||||
});
|
||||
|
||||
expect(
|
||||
readProjectConfiguration(tree, 'react-app').targets['serve-static-custom']
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"executor": "@nrwl/web:file-server",
|
||||
"options": Object {
|
||||
"buildTarget": "react-app:build",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should infer outputPath via the buildTarget#outputs', () => {
|
||||
addAngularConfig(tree, 'angular-app');
|
||||
const projectConfig = readProjectConfiguration(tree, 'angular-app');
|
||||
delete projectConfig.targets.build.options.outputPath;
|
||||
projectConfig.targets.build.outputs = ['{options.myPath}'];
|
||||
projectConfig.targets.build.options.myPath = 'dist/apps/angular-app';
|
||||
|
||||
updateProjectConfiguration(tree, 'angular-app', projectConfig);
|
||||
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: 'angular-app:build',
|
||||
});
|
||||
|
||||
expect(
|
||||
readProjectConfiguration(tree, 'angular-app').targets['serve-static']
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"executor": "@nrwl/web:file-server",
|
||||
"options": Object {
|
||||
"buildTarget": "angular-app:build",
|
||||
"staticFilePath": "dist/apps/angular-app",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not override targets', () => {
|
||||
addStorybookConfig(tree, 'storybook');
|
||||
|
||||
const pc = readProjectConfiguration(tree, 'storybook');
|
||||
pc.targets['serve-static'] = {
|
||||
executor: 'custom:executor',
|
||||
};
|
||||
|
||||
updateProjectConfiguration(tree, 'storybook', pc);
|
||||
|
||||
expect(() => {
|
||||
webStaticServeGenerator(tree, {
|
||||
buildTarget: 'storybook:build-storybook',
|
||||
});
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"Project storybook already has a 'serve-static' target configured.
|
||||
Either rename or remove the existing 'serve-static' target and try again.
|
||||
Optionally, you can provide a different name with the --target-name option other than 'serve-static'"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
function addReactConfig(tree: Tree, name: string) {
|
||||
addProjectConfiguration(tree, name, {
|
||||
name,
|
||||
projectType: 'application',
|
||||
root: `apps/${name}`,
|
||||
sourceRoot: `apps/${name}/src`,
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nrwl/vite:build',
|
||||
outputs: ['{options.outputPath}'],
|
||||
defaultConfiguration: 'production',
|
||||
options: {
|
||||
outputPath: `dist/apps/${name}`,
|
||||
},
|
||||
configurations: {
|
||||
development: {
|
||||
mode: 'development',
|
||||
},
|
||||
production: {
|
||||
mode: 'production',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function addAngularConfig(tree: Tree, name: string) {
|
||||
addProjectConfiguration(tree, name, {
|
||||
name,
|
||||
projectType: 'application',
|
||||
root: `apps/${name}`,
|
||||
sourceRoot: `apps/${name}/src`,
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@angular-devkit/build-angular:browser',
|
||||
outputs: ['{options.outputPath}'],
|
||||
options: {
|
||||
outputPath: `dist/apps/${name}`,
|
||||
index: `apps/${name}/src/index.html`,
|
||||
main: `apps/${name}/src/main.ts`,
|
||||
polyfills: [`zone.js`],
|
||||
tsConfig: `apps/${name}/tsconfig.app.json`,
|
||||
inlineStyleLanguage: `scss`,
|
||||
assets: [`apps/${name}/src/favicon.ico`, `apps/${name}/src/assets`],
|
||||
styles: [`apps/${name}/src/styles.scss`],
|
||||
scripts: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function addStorybookConfig(tree: Tree, name: string) {
|
||||
addProjectConfiguration(tree, name, {
|
||||
name,
|
||||
projectType: 'application',
|
||||
root: `apps/${name}`,
|
||||
sourceRoot: `apps/${name}/src`,
|
||||
targets: {
|
||||
'build-storybook': {
|
||||
executor: '@storybook/angular:build-storybook',
|
||||
outputs: ['{options.outputDir}'],
|
||||
options: {
|
||||
outputDir: `dist/apps/${name}/storybook`,
|
||||
configDir: `apps/${name}/.storybook`,
|
||||
browserTarget: `storybook:build-storybook`,
|
||||
compodoc: false,
|
||||
},
|
||||
configurations: {
|
||||
ci: {
|
||||
quiet: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
import {
|
||||
convertNxGenerator,
|
||||
logger,
|
||||
parseTargetString,
|
||||
readProjectConfiguration,
|
||||
stripIndents,
|
||||
TargetConfiguration,
|
||||
Tree,
|
||||
updateProjectConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { Schema as FileServerExecutorSchema } from '../../executors/file-server/schema.d';
|
||||
interface WebStaticServeSchema {
|
||||
buildTarget: string;
|
||||
outputPath?: string;
|
||||
targetName?: string;
|
||||
}
|
||||
|
||||
interface NormalizedWebStaticServeSchema extends WebStaticServeSchema {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}
|
||||
|
||||
export function webStaticServeGenerator(
|
||||
tree: Tree,
|
||||
options: WebStaticServeSchema
|
||||
) {
|
||||
const opts = normalizeOptions(tree, options);
|
||||
addStaticConfig(tree, opts);
|
||||
}
|
||||
|
||||
function normalizeOptions(
|
||||
tree: Tree,
|
||||
options: WebStaticServeSchema
|
||||
): NormalizedWebStaticServeSchema {
|
||||
const target = parseTargetString(options.buildTarget);
|
||||
const opts: NormalizedWebStaticServeSchema = {
|
||||
...options,
|
||||
targetName: options.targetName || 'serve-static',
|
||||
projectName: target.project,
|
||||
};
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, target.project);
|
||||
const buildTargetConfig = projectConfig?.targets?.[target.target];
|
||||
if (!buildTargetConfig) {
|
||||
throw new Error(stripIndents`Unable to read the target configuration for the provided build target, ${opts.buildTarget}
|
||||
Are you sure this target exists?`);
|
||||
}
|
||||
|
||||
if (projectConfig.targets[opts.targetName]) {
|
||||
throw new Error(stripIndents`Project ${target.project} already has a '${opts.targetName}' target configured.
|
||||
Either rename or remove the existing '${opts.targetName}' target and try again.
|
||||
Optionally, you can provide a different name with the --target-name option other than '${opts.targetName}'`);
|
||||
}
|
||||
|
||||
// NOTE: @nrwl/web:file-server only looks for the outputPath option
|
||||
if (!buildTargetConfig.options?.outputPath && !opts.outputPath) {
|
||||
// attempt to find the suiteable path from the outputs
|
||||
let maybeOutputValue: any;
|
||||
for (const o of buildTargetConfig?.outputs || []) {
|
||||
const isInterpolatedOutput = o.trim().startsWith('{options.');
|
||||
if (!isInterpolatedOutput) {
|
||||
continue;
|
||||
}
|
||||
const noBracketParts = o.replace(/[{}]/g, '').split('.');
|
||||
|
||||
if (noBracketParts.length === 2 && noBracketParts?.[1]) {
|
||||
const key = noBracketParts[1].trim();
|
||||
const value = buildTargetConfig.options?.[key];
|
||||
if (value) {
|
||||
maybeOutputValue = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: outputDir is the storybook option.
|
||||
opts.outputPath = buildTargetConfig.options?.outputDir || maybeOutputValue;
|
||||
if (opts.outputPath) {
|
||||
logger.warn(`Automatically detected the output path to be ${opts.outputPath}.
|
||||
If this is incorrect, the update the staticFilePath option in the ${target.project}:${opts.targetName} target configuration`);
|
||||
} else {
|
||||
logger.warn(
|
||||
stripIndents`${opts.buildTarget} did not have an outputPath property set and --output-path was not provided.
|
||||
Without either options, the static serve will most likely be unable to serve your project.
|
||||
It's recommend to provide a --output-path option in this case.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
function addStaticConfig(tree: Tree, opts: NormalizedWebStaticServeSchema) {
|
||||
const projectConfig = readProjectConfiguration(tree, opts.projectName);
|
||||
|
||||
const staticServeOptions: TargetConfiguration<
|
||||
Partial<FileServerExecutorSchema>
|
||||
> = {
|
||||
executor: '@nrwl/web:file-server',
|
||||
options: {
|
||||
buildTarget: opts.buildTarget,
|
||||
staticFilePath: opts.outputPath,
|
||||
},
|
||||
};
|
||||
|
||||
projectConfig.targets[opts.targetName] = staticServeOptions;
|
||||
|
||||
updateProjectConfiguration(tree, opts.projectName, projectConfig);
|
||||
}
|
||||
|
||||
export const compat = convertNxGenerator(webStaticServeGenerator);
|
||||
export default webStaticServeGenerator;
|
||||
Loading…
x
Reference in New Issue
Block a user