Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ tests/distribution/vite-plugin-typo3*.tgz
tests/distribution/**/public
tests/integration/project/public
tests/integration/project/packages/test_extension/Resources/Public
tests/integration/uninitializedProject/public
tests/integration/uninitializedProject/packages/test_extension/Resources/Public
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ You can provide additional configuration to the plugin, for example:
and `@` are created for all available extension folders. If set to `@`, only `@` aliases
are created, if set to `EXT`, only `EXT:` aliases are created. If set to `false`, alias
creation is skipped altogether.
- `composerPackagePaths` (array of paths, default `null`): If specified, the automatic
discovery of composer packages via `vendor/composer/installed.json` will be skipped. Instead,
the specified paths will be scanned for `composer.json` files to be used instead. This can
be helpful in CI scenarios where PHP dependencies aren't available in the frontend build
step.

### Fixing CORS issues

Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface UserConfig {
target?: PluginTarget;
entrypointFile?: string;
entrypointIgnorePatterns?: string[];
composerPackagePaths?: string[];
debug?: boolean;
aliases?: AliasConfig;
}
Expand All @@ -10,6 +11,7 @@ export interface PluginConfig<T extends ComposerContext> extends UserConfig {
target: PluginTarget;
entrypointFile: string;
composerContext: T;
composerPackagePaths?: string[];
entrypointIgnorePatterns: string[];
debug: boolean;
aliases: AliasConfig;
Expand Down
18 changes: 14 additions & 4 deletions src/typo3project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import type {
import {
addAliases,
addRollupInputs,
determineAvailableTypo3Extensions,
determineAvailableTypo3ExtensionsFromComposer,
determineAvailableTypo3ExtensionsFromPaths,
findEntrypointsInExtensions,
getDefaultAllowedOrigins,
getDefaultIgnoreList,
Expand Down Expand Up @@ -90,9 +91,18 @@ export default function typo3project(
);

// Extract relevant TYPO3 extensions from composer metadata
availableExtensions = determineAvailableTypo3Extensions(
pluginConfig.composerContext,
);
if (pluginConfig.composerPackagePaths) {
availableExtensions =
determineAvailableTypo3ExtensionsFromPaths(
config.root ?? process.cwd(),
pluginConfig.composerPackagePaths,
);
} else {
availableExtensions =
determineAvailableTypo3ExtensionsFromComposer(
pluginConfig.composerContext,
);
}

// Add path alias for each extension
config.resolve ??= {};
Expand Down
26 changes: 25 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function findEntrypointsInExtensions(
return entrypoints;
}

export function determineAvailableTypo3Extensions(
export function determineAvailableTypo3ExtensionsFromComposer(
composerContext: Typo3ProjectContext,
): Typo3ExtensionContext[] {
const composerInstalled = join(
Expand Down Expand Up @@ -179,6 +179,30 @@ export function determineAvailableTypo3Extensions(
return installedExtensions;
}

export function determineAvailableTypo3ExtensionsFromPaths(
rootPath: string,
composerPackagePaths: string[],
): Typo3ExtensionContext[] {
return composerPackagePaths
.map((path: string) => resolve(rootPath, path))
.map((absolutePath: string) => {
const composerFile = absolutePath + "/composer.json";
if (!fs.existsSync(composerFile)) {
throw new Error(
`Invalid composer package in "${absolutePath}", composer.json not found.`,
);
}
return createComposerContext(
readJsonFile(composerFile),
absolutePath,
);
})
.filter(
(context: ComposerContext) =>
context.type === "typo3-cms-extension",
) as Typo3ExtensionContext[];
}

export function outputDebugInformation(
availableExtensions: Typo3ExtensionContext[],
entrypoints: string[],
Expand Down
45 changes: 30 additions & 15 deletions tests/integration/typo3extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,28 @@ import { build } from "vite";
import typo3 from "../../src";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { UserConfig } from "../../src/types";

const __dirname = fileURLToPath(new URL(".", import.meta.url));

test("vite build works for TYPO3 extension", async () => {
const root = join(__dirname, "project/packages/test_extension");
interface TestCase {
projectName: string;
pluginConfig: UserConfig;
}

test.for<TestCase>([
{ projectName: "project", pluginConfig: {} },
{ projectName: "uninitializedProject", pluginConfig: {} },
])("vite build works for TYPO3 extension in $projectName", async (testCase) => {
const root = join(
__dirname,
`${testCase.projectName}/packages/test_extension`,
);
const output = await build({
root,
plugins: [typo3({ target: "extension" })],
plugins: [typo3({ target: "extension", ...testCase.pluginConfig })],
build: {
// @ts-expect-error "entry" is specified by plugin, so no need to specify it here
lib: {
cssFileName: "style",
},
Expand All @@ -26,21 +39,23 @@ test("vite build works for TYPO3 extension", async () => {
expect(esmOutput.output[0].fileName).toMatchInlineSnapshot(
`"Alt.entry.js"`,
);
expect(esmOutput.output[0].code).toMatchInlineSnapshot(`
"//#region tests/integration/project/packages/test_extension/Resources/Private/Alt.entry.ts
console.log("Alt.entry.ts");
//#endregion
"
`);
expect(esmOutput.output[0].code).toMatch(
`
//#region tests/integration/${testCase.projectName}/packages/test_extension/Resources/Private/Alt.entry.ts
console.log("Alt.entry.ts");
//#endregion
`.trim(),
);
expect(esmOutput.output[1].fileName).toMatchInlineSnapshot(
`"Main.entry.js"`,
);
expect(esmOutput.output[1].code).toMatchInlineSnapshot(`
"//#region tests/integration/project/packages/test_extension/Resources/Private/JavaScript/Main.ts
console.log("Main.ts");
//#endregion
"
`);
expect(esmOutput.output[1].code).toMatch(
`
//#region tests/integration/${testCase.projectName}/packages/test_extension/Resources/Private/JavaScript/Main.ts
console.log("Main.ts");
//#endregion
`.trim(),
);
expect(esmOutput.output[2].fileName).toMatchInlineSnapshot(`"style.css"`);
expect(esmOutput.output[2].source).toMatchInlineSnapshot(`
"body{background:red}
Expand Down
25 changes: 21 additions & 4 deletions tests/integration/typo3project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,32 @@
import typo3 from "../../src";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { RolldownOutput, OutputChunk, OutputAsset } from "rolldown";
import type { RolldownOutput, OutputChunk, OutputAsset } from "rolldown";
import { UserConfig } from "../../src/types";

const __dirname = fileURLToPath(new URL(".", import.meta.url));

test("vite build works for TYPO3 project", async () => {
const root = join(__dirname, "project");
interface TestCase {
projectName: string;
pluginConfig: UserConfig;
}

test.for<TestCase>([
{ projectName: "project", pluginConfig: {} },
{
projectName: "uninitializedProject",
pluginConfig: {
composerPackagePaths: [
"packages/test_extension/",
"vendor/test-vendor/vendor-extension/",
],
},
},
])("vite build works for TYPO3 project $projectName", async (testCase) => {
const root = join(__dirname, testCase.projectName);
const output = (await build({
root,
plugins: [typo3()],
plugins: [typo3(testCase.pluginConfig)],
})) as RolldownOutput;

const sortedOutput: (OutputAsset | OutputChunk)[] = output.output.sort(
Expand All @@ -23,7 +40,7 @@
expect((sortedOutput[0] as OutputAsset).fileName).toBe(
".vite/manifest.json",
);
await expect((sortedOutput[0] as OutputAsset).source).toMatchFileSnapshot(

Check failure on line 43 in tests/integration/typo3project.test.ts

View workflow job for this annotation

GitHub Actions / test

tests/integration/typo3project.test.ts > vite build works for TYPO3 project 'uninitializedProject'

Error: Snapshot `vite build works for TYPO3 project 'uninitializedProject' 1` mismatched - Expected + Received @@ -1,16 +1,16 @@ { - "vendor/praetorius/test-extension/Resources/Private/Alt.entry.ts": { + "packages/test_extension/Resources/Private/Alt.entry.ts": { "file": "assets/Alt.entry-BoPKGVcr.js", "name": "Alt.entry", - "src": "vendor/praetorius/test-extension/Resources/Private/Alt.entry.ts", + "src": "packages/test_extension/Resources/Private/Alt.entry.ts", "isEntry": true }, - "vendor/praetorius/test-extension/Resources/Private/Main.entry.ts": { + "packages/test_extension/Resources/Private/Main.entry.ts": { "file": "assets/Main.entry-49EpidS1.js", "name": "Main.entry", - "src": "vendor/praetorius/test-extension/Resources/Private/Main.entry.ts", + "src": "packages/test_extension/Resources/Private/Main.entry.ts", "isEntry": true, "css": [ "assets/Main-VWk4xp3e.css" ] }, ❯ tests/integration/typo3project.test.ts:43:5

Check failure on line 43 in tests/integration/typo3project.test.ts

View workflow job for this annotation

GitHub Actions / test

tests/integration/typo3project.test.ts > vite build works for TYPO3 project 'uninitializedProject'

Error: Snapshot `vite build works for TYPO3 project 'uninitializedProject' 1` mismatched - Expected + Received @@ -1,16 +1,16 @@ { - "vendor/praetorius/test-extension/Resources/Private/Alt.entry.ts": { + "packages/test_extension/Resources/Private/Alt.entry.ts": { "file": "assets/Alt.entry-BoPKGVcr.js", "name": "Alt.entry", - "src": "vendor/praetorius/test-extension/Resources/Private/Alt.entry.ts", + "src": "packages/test_extension/Resources/Private/Alt.entry.ts", "isEntry": true }, - "vendor/praetorius/test-extension/Resources/Private/Main.entry.ts": { + "packages/test_extension/Resources/Private/Main.entry.ts": { "file": "assets/Main.entry-49EpidS1.js", "name": "Main.entry", - "src": "vendor/praetorius/test-extension/Resources/Private/Main.entry.ts", + "src": "packages/test_extension/Resources/Private/Main.entry.ts", "isEntry": true, "css": [ "assets/Main-VWk4xp3e.css" ] }, ❯ tests/integration/typo3project.test.ts:43:5
"manifest-expected.json",
);

Expand Down
3 changes: 3 additions & 0 deletions tests/integration/uninitializedProject/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "project"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["../Resources/Private/*.entry.ts"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Alt.entry.ts");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background: red;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Main.ts");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import "./Css/Main.css";
import "./JavaScript/Main.ts";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "praetorius/test-extension",
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "test_extension"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["../Resources/Private/Vendor.entry.ts"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background: blue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Vendor.ts");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import "./Css/Vendor.css";
import "./JavaScript/Vendor.ts";
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "vendor_extension"
}
}
}
50 changes: 44 additions & 6 deletions tests/unit/determineRelevantTypo3Extensions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, test, expect, vi, beforeEach } from "vitest";
import { determineAvailableTypo3Extensions } from "../../src/utils";
import {
determineAvailableTypo3ExtensionsFromComposer,
determineAvailableTypo3ExtensionsFromPaths,
} from "../../src/utils";
import { vol } from "memfs";
import fixtureDirectoryStructure from "./fixtureDirectoryStructure";

Expand All @@ -10,10 +13,10 @@ beforeEach(() => {
vol.fromJSON(fixtureDirectoryStructure);
});

describe("determineAvailableTypo3Extensions", () => {
test("determineAvailableTypo3Extensions", () => {
describe("determineAvailableTypo3ExtensionsFromComposer", () => {
test("determineAvailableTypo3ExtensionsFromComposer", () => {
expect(
determineAvailableTypo3Extensions({
determineAvailableTypo3ExtensionsFromComposer({
type: "project",
path: "/path/to/fixtures/composerProject",
vendorDir: "vendor",
Expand All @@ -35,7 +38,7 @@ describe("determineAvailableTypo3Extensions", () => {

test("no vendor path", () => {
expect(() => {
determineAvailableTypo3Extensions({
determineAvailableTypo3ExtensionsFromComposer({
type: "project",
path: "/path/to/fixtures/composerProjectWIthoutVendor",
vendorDir: "vendor",
Expand All @@ -46,7 +49,7 @@ describe("determineAvailableTypo3Extensions", () => {

test("invalid installed.json", () => {
expect(() => {
determineAvailableTypo3Extensions({
determineAvailableTypo3ExtensionsFromComposer({
type: "project",
path: "/path/to/fixtures/composerProjectInvalidVendor",
vendorDir: "vendor",
Expand All @@ -55,3 +58,38 @@ describe("determineAvailableTypo3Extensions", () => {
}).toThrow();
});
});

describe("determineAvailableTypo3ExtensionsFromPaths", () => {
test("determineAvailableTypo3ExtensionsFromPaths", () => {
expect(
determineAvailableTypo3ExtensionsFromPaths(
"/path/to/fixtures/uninitializedComposerProject",
[
"packages/composerExtension",
"packages/composerExtension2",
"packages/library",
],
),
).toEqual([
{
type: "typo3-cms-extension",
extensionKey: "composer_extension",
path: "/path/to/fixtures/uninitializedComposerProject/packages/composerExtension",
},
{
type: "typo3-cms-extension",
extensionKey: "namespace_extension",
path: "/path/to/fixtures/uninitializedComposerProject/packages/composerExtension2",
},
]);
});

test("invalid composer package", () => {
expect(() => {
determineAvailableTypo3ExtensionsFromPaths(
"/path/to/fixtures/uninitializedComposerProject",
["some/random/folder"],
);
}).toThrow();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "project"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["../Resources/Private/Main.entry.js"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Main.entry.js");
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "composer_extension"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["../Resources/Private/Main.entry.js"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Main.entry.js");
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "typo3-cms-extension",
"extra": {
"typo3/cms": {
"extension-key": "namespace_extension"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "library"
}
Loading