Skip to content

Commit

Permalink
fix(build): fix tailwind external subdependencies during standalone b…
Browse files Browse the repository at this point in the history
…uild (#638)

* fix(build): fix tailwind external subdependencies during standalone build

* test: restore mocks

* feat: re-implement to improve build time

* fix: resolve all lightningcss platforms
  • Loading branch information
aralroca authored Nov 19, 2024
1 parent 6df8217 commit 95e6703
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 9 deletions.
Binary file modified bun.lockb
Binary file not shown.
5 changes: 3 additions & 2 deletions docs/building-your-application/configuring/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ export type Integration = {
defaultCSS?: {
content: string;
applyDefaultWhenEvery: (content: string) => boolean;
}}
```
}};
afterBuild?(brisaConstants: BrisaConstants): void | Promise<void>;
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"www:build": "bun run --cwd packages/www build",
"www:dev": "bun run --cwd packages/www dev",
"www:deploy": "bun run build && bun run www:build && vercel --prod",
"prepare": "husky"
"prepare": "husky && bun run packages/brisa-tailwindcss/scripts/generate-json-deps.ts"
},
"packageManager": "[email protected]",
"engines": {
Expand Down
1 change: 1 addition & 0 deletions packages/brisa-tailwindcss/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ export default function tailwindCSS(): {
content: string;
applyDefaultWhenEvery: (content: string) => boolean;
};
afterBuild(brisaConstants: BrisaConstants): void | Promise<void>;
};
39 changes: 38 additions & 1 deletion packages/brisa-tailwindcss/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { describe, expect, it } from 'bun:test';
import { describe, expect, it, spyOn } from 'bun:test';
import fs from 'node:fs/promises';
import packageJSON from './package.json';
import libs from './libs.json';
import brisaTailwindcss from '.';

const TAILWIND_VERSION = packageJSON.devDependencies.tailwindcss;

describe('brisa-tailwindcss', () => {
it('should return the correct name', () => {
const integration = brisaTailwindcss();
Expand Down Expand Up @@ -75,4 +80,36 @@ describe('brisa-tailwindcss', () => {

expect(transpiledCSS).not.toContain('@layer base');
});

it('should call Bun.$ to install tailwindcss inside the build folder #637', async () => {
const integration = brisaTailwindcss();
const mockLog = spyOn(console, 'log');
const mockCp = spyOn(fs, 'cp').mockResolvedValue();
const mockExists = spyOn(fs, 'exists').mockResolvedValue(true);

await integration.afterBuild({
BUILD_DIR: import.meta.dirname,
LOG_PREFIX: { INFO: 'INFO', WAIT: 'WAIT', TICK: 'TICK' },
});

expect(mockLog.mock.calls[0][0]).toBe('INFO');
expect(mockLog.mock.calls[1]).toEqual([
'WAIT',
' Embedding TailwindCSS in the build folder...',
]);
expect(mockCp).toHaveBeenCalledTimes(libs.length);
for (const lib of libs) {
expect(mockCp).toHaveBeenCalledWith(
expect.stringContaining(lib),
expect.stringContaining(lib),
{ recursive: true },
);
}
expect(mockLog.mock.calls[2][0]).toBe('INFO');
expect(mockLog.mock.calls[2][1]).toBe('TICK');
expect(mockLog.mock.calls[2][2]).toContain('TailwindCSS embedded in');
mockLog.mockRestore();
mockCp.mockRestore();
mockExists.mockRestore();
});
});
38 changes: 38 additions & 0 deletions packages/brisa-tailwindcss/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import tailwindcss from '@tailwindcss/postcss';
import postcss from 'postcss';
import fs from 'node:fs/promises';
import path from 'node:path';
import libs from './libs.json';

// Note: is not bundled here to avoid issues with lightningcss
export default function brisaTailwindcss() {
Expand All @@ -20,5 +23,40 @@ export default function brisaTailwindcss() {
`,
applyDefaultWhenEvery: (content: string) => !content.includes('tailwind'),
},
// Tailwind has a subdependency called lightwindcss, which cannot be included in the bundle
// https://github.com/parcel-bundler/lightningcss/issues/701
// then as a solution is to put it as "external" so the build works correctly. However, for the standalone
// build to continue being standalone, it needs this dependency along with all its subdependencies to
// work correctly. As a solution, we install tailwind in the build folder and in this way the problem is solved.
// Issue: https://github.com/brisa-build/brisa/issues/637
async afterBuild({ BUILD_DIR, LOG_PREFIX }) {
const start = Date.now();
const destNodeModules = path.join(BUILD_DIR, 'node_modules');
const nodeModules = Bun.resolveSync('brisa', BUILD_DIR).split('brisa')[0];

console.log(LOG_PREFIX.INFO, '');
console.log(
LOG_PREFIX.WAIT,
' Embedding TailwindCSS in the build folder...',
);

await Promise.all(
libs.map(async (lib) => {
const from = path.join(nodeModules, lib);
const to = path.join(destNodeModules, lib);

if (await fs.exists(from)) {
return fs.cp(from, to, { recursive: true });
}
}),
);

const milliseconds = Date.now() - start;
console.log(
LOG_PREFIX.INFO,
LOG_PREFIX.TICK,
`TailwindCSS embedded in ${milliseconds}ms`,
);
},
};
}
25 changes: 25 additions & 0 deletions packages/brisa-tailwindcss/libs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
"lightningcss-darwin-x64",
"lightningcss-linux-x64-gnu",
"lightningcss-win32-x64-msvc",
"lightningcss-win32-arm64-msvc",
"lightningcss-darwin-arm64",
"lightningcss-linux-arm64-gnu",
"lightningcss-linux-arm-gnueabihf",
"lightningcss-linux-arm64-musl",
"lightningcss-linux-x64-musl",
"lightningcss-freebsd-x64",
"@alloc",
"tapable",
"jiti",
"detect-libc",
"nanoid",
"@tailwindcss",
"postcss",
"enhanced-resolve",
"picocolors",
"tailwindcss",
"graceful-fs",
"lightningcss",
"source-map-js"
]
5 changes: 4 additions & 1 deletion packages/brisa-tailwindcss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
"name": "Brisa Team",
"email": "[email protected]"
},
"scripts": {
"libs-json": "bun run scripts/generate-json-deps.ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/brisa-build/brisa.git",
"directory": "packages/brisa-tailwindcss"
},
"files": ["index.ts", "index.d.ts"],
"files": ["index.ts", "index.d.ts", "libs.json"],
"devDependencies": {
"@tailwindcss/postcss": "4.0.0-alpha.33",
"postcss": "8.4.49",
Expand Down
39 changes: 39 additions & 0 deletions packages/brisa-tailwindcss/scripts/generate-json-deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from 'node:fs';
import path from 'node:path';
import packageJSON from '../package.json';

const TAILWIND_VERSION = packageJSON.devDependencies.tailwindcss;
const tempDir = path.join(import.meta.dirname, 'temp');

fs.mkdirSync(tempDir);
fs.writeFileSync(
path.join(tempDir, 'package.json'),
JSON.stringify({
dependencies: {
tailwindcss: TAILWIND_VERSION,
'@tailwindcss/postcss': TAILWIND_VERSION,
},
}),
);

await Bun.$`cd ${tempDir} && bun i`.quiet();

const nodeModules = path.join(tempDir, 'node_modules');
const lightningcssPackage = JSON.parse(
fs.readFileSync(
path.join(nodeModules, 'lightningcss', 'package.json'),
'utf-8',
),
);
const libs = new Set(Object.keys(lightningcssPackage.optionalDependencies));

for (const lib of fs.readdirSync(nodeModules)) {
if (lib.startsWith('.')) continue;
libs.add(lib);
}

fs.writeFileSync(
path.join(import.meta.dirname, '..', 'libs.json'),
JSON.stringify([...libs]),
);
fs.rmSync(tempDir, { recursive: true });
1 change: 1 addition & 0 deletions packages/brisa/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,7 @@ export type Integration = {
content: string;
applyDefaultWhenEvery: (content: string) => boolean;
};
afterBuild?(brisaConstants: BrisaConstants): void | Promise<void>;
};

export interface I18nDictionary {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { expect, it, describe, beforeEach, afterEach, spyOn } from 'bun:test';
import {
expect,
it,
describe,
beforeEach,
afterEach,
spyOn,
mock,
} from 'bun:test';
import path from 'node:path';
import fs from 'node:fs';
import compileBrisaInternalsToDoBuildPortable from '.';
Expand Down Expand Up @@ -205,4 +213,19 @@ describe('utils/compileServeInternalsIntoBuild', () => {
},
});
});

it('should call all afterBuild functions inside integrations', async () => {
const mockAfterBuild = mock(async () => {});
const mockAfterBuild2 = mock(() => {});

mockConstants.CONFIG.integrations = [
{ afterBuild: mockAfterBuild },
{ afterBuild: mockAfterBuild2 },
];

await compileBrisaInternalsToDoBuildPortable();

expect(mockAfterBuild).toHaveBeenCalledWith(mockConstants);
expect(mockAfterBuild2).toHaveBeenCalledWith(mockConstants);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ const NO_SERVER_EXPORTS = new Set([
*
*/
export default async function compileServeInternalsIntoBuild() {
const constants = getConstants();
const { BUILD_DIR, LOG_PREFIX, CONFIG, ROOT_DIR, IS_PRODUCTION, BRISA_DIR } =
getConstants();
constants;

if (!IS_PRODUCTION) return;

Expand Down Expand Up @@ -88,6 +89,14 @@ export default async function compileServeInternalsIntoBuild() {
createBrisaModule(runtimeExec);
addBrisaModule();

const afterBuildFunctions = (CONFIG.integrations ?? []).filter(
(i) => i.afterBuild,
);

if (afterBuildFunctions.length) {
await Promise.all(afterBuildFunctions.map((i) => i.afterBuild!(constants)));
}

if (isServer) {
const relativeServerFilePath = path.join(
path.relative(ROOT_DIR, BUILD_DIR),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const i18nCode = 2799;
const brisaSize = 5720; // TODO: Reduce this size :/
const webComponents = 1107;
const unsuspenseSize = 213;
const rpcSize = 2509; // TODO: Reduce this size
const rpcSize = 2500; // TODO: Reduce this size
const lazyRPCSize = 4105; // TODO: Reduce this size
// lazyRPC is loaded after user interaction (action, link),
// so it's not included in the initial size
Expand Down
2 changes: 1 addition & 1 deletion packages/www/src/public/content.json
Original file line number Diff line number Diff line change
Expand Up @@ -3359,7 +3359,7 @@
{
"id": "/building-your-application/configuring/integrations#types",
"title": "Types",
"text": "export type Configuration = {\n // ...\n integrations?: Integration[];\n};\n\nexport type Integration = {\n name: string;\n transpileCSS?(pathname: string, content: string): Promise<string>;\n defaultCSS?: {\n content: string;\n applyDefaultWhenEvery: (content: string) => boolean;\n }}",
"text": "export type Configuration = {\n // ...\n integrations?: Integration[];\n};\n\nexport type Integration = {\n name: string;\n transpileCSS?(pathname: string, content: string): Promise<string>;\n defaultCSS?: {\n content: string;\n applyDefaultWhenEvery: (content: string) => boolean;\n }};\n afterBuild?(brisaConstants: BrisaConstants): void | Promise<void>;",
"titles": [
"Integrations"
]
Expand Down

0 comments on commit 95e6703

Please sign in to comment.