Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle external CSS #128

Merged
merged 8 commits into from
Jul 28, 2023
Merged
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
41 changes: 41 additions & 0 deletions .changeset/css-exports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
'@crackle/core': minor
---

Crackle now supports importing external CSS.

This is useful when consuming packages which come with their own CSS, such as [Pure React Carousel](https://github.com/express-labs/pure-react-carousel).

External CSS can be imported with a side-effect `import`, same as how you would import a JavaScript or TypeScript module:

```tsx
// src/components/MyComponent.tsx
import 'package-with-styles/dist/styles.css';

import { Component } from 'package-with-styles';

export const MyComponent = (props) => {
<Component {...props} />;
};
```

The side-effect import will be preserved in the output bundles.

External CSS can also be imported with a CSS `@import` rule:

```css
/* src/components/MyComponent/third-party.css */
@import 'package-with-styles/dist/styles.css';
```

```tsx
// src/components/MyComponent.tsx
import './third-party.css';

export const MyComponent = () => {
// ...
};
```

When importing with a CSS `@import` rule, Crackle will bundle all external CSS into one file and output it to the `dist` directory.
Package exports will also be updated so consumers can import the bundled CSS.
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ fixtures/with-side-effects/dist
fixtures/with-side-effects/reset
# end managed by crackle

# managed by crackle
fixtures/with-styles/dist
# end managed by crackle

# managed by crackle
fixtures/with-vocab/dist
# end managed by crackle
Expand Down
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ fixtures/with-side-effects/dist
fixtures/with-side-effects/reset
# end managed by crackle

# managed by crackle
fixtures/with-styles/dist
# end managed by crackle

# managed by crackle
fixtures/with-vocab/dist
# end managed by crackle
Expand Down
3 changes: 3 additions & 0 deletions fixtures/with-styles/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# managed by crackle
/dist
# end managed by crackle
3 changes: 3 additions & 0 deletions fixtures/with-styles/package-with-styles/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "@sku-fixtures/package-with-styles"
}
4 changes: 4 additions & 0 deletions fixtures/with-styles/package-with-styles/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.external {
display: none;
font-size: 9px;
}
36 changes: 36 additions & 0 deletions fixtures/with-styles/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@crackle-fixtures/with-styles",
"version": "1.0.0",
"private": true,
"license": "MIT",
"author": "SEEK",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./dist/style.css": "./dist/style.css",
"./package.json": "./package.json"
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist",
"init"
],
"scripts": {
"dev": "crackle dev",
"fix": "crackle fix",
"package": "crackle package"
},
"dependencies": {
"@crackle-fixtures/package-with-styles": "link:./package-with-styles",
"@vanilla-extract/css": "^1.12.0",
"react": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.21"
}
}
5 changes: 5 additions & 0 deletions fixtures/with-styles/src/Component.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { style } from '@vanilla-extract/css';

export const redBorder = style({
border: '5px solid red',
});
11 changes: 11 additions & 0 deletions fixtures/with-styles/src/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import '@crackle-fixtures/package-with-styles/styles.css';

import './thirdparty.css';

import * as styles from './Component.css';

export default () => (
<div className={styles.redBorder}>
<span className="external" />
</div>
);
1 change: 1 addition & 0 deletions fixtures/with-styles/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Component } from './Component';
1 change: 1 addition & 0 deletions fixtures/with-styles/src/thirdparty.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import '@crackle-fixtures/package-with-styles/styles.css';
20 changes: 17 additions & 3 deletions packages/core/src/entries/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import fs from 'fs/promises';
import path from 'path';

import chalk from 'chalk';
import type { RollupOutput } from 'rollup';

import { type EnhancedConfig, type PartialConfig, getConfig } from '../config';
import { distDir } from '../constants';
import { createBundle } from '../package-utils/bundle';
import { createDtsBundle } from '../package-utils/dts';
import { renderPackageJsonValidationError } from '../reporters/package';
Expand All @@ -15,7 +17,10 @@ import {
getPackageEntryPoints,
} from '../utils/entry-points';
import { updateGitignore } from '../utils/gitignore';
import { validatePackageJson } from '../utils/setup-package-json';
import {
updatePackageJsonExports,
validatePackageJson,
} from '../utils/setup-package-json';

import { fix } from './fix';
import { logger } from './logger';
Expand Down Expand Up @@ -62,11 +67,12 @@ const build = async (config: EnhancedConfig, packageName: string) => {
label: string,
) => {
logger.info(`⚙️ Creating ${chalk.bold(label)} bundle...`);
await bundle(config, entries);
const result = await bundle(config, entries);
logger.info(`⚙️ Finished creating ${chalk.bold(label)} bundle`);
return result;
};

await Promise.all([
const [bundles] = await Promise.all([
withLogging(createBundle, 'esm/cjs'),
withLogging(createDtsBundle, 'dts'),
]);
Expand All @@ -75,6 +81,14 @@ const build = async (config: EnhancedConfig, packageName: string) => {

await updateGitignore(config.root, entries);

const cssExports = (bundles as RollupOutput[])
.flatMap((bundle) => bundle.output)
.map((output) => output.fileName)
.filter((fileName) => fileName.endsWith('.css'))
.map((fileName) => path.join(distDir, fileName));

await updatePackageJsonExports(config.root, cssExports);

logger.info(`✅ Successfully built ${chalk.bold.green(packageName)}!`);
};

Expand Down
11 changes: 7 additions & 4 deletions packages/core/src/package-utils/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path';

import { cssFileFilter as vanillaCssFileFilter } from '@vanilla-extract/integration';
import fse from 'fs-extra';
import type { OutputOptions } from 'rollup';
import type { OutputOptions, RollupOutput } from 'rollup';
import { normalizePath, build as viteBuild } from 'vite';

import type { EnhancedConfig } from '../config';
Expand Down Expand Up @@ -82,7 +82,7 @@ export const createBundle = async (
} satisfies OutputOptions;
};

await viteBuild({
const result = (await viteBuild({
...commonViteConfig,
esbuild: {
jsx: 'automatic',
Expand All @@ -105,7 +105,8 @@ export const createBundle = async (
minify: false,
rollupOptions: {
treeshake: {
moduleSideEffects: 'no-external',
// keep only CSS side-effect imports
moduleSideEffects: (id, external) => !external || id.endsWith('.css'),
},
output: formats.map((format) => createOutputOptionsForFormat(format)),
onLog(level, log, defaultHandler) {
Expand All @@ -114,5 +115,7 @@ export const createBundle = async (
},
},
},
});
})) as RollupOutput[]; // because we know that we're building esm and cjs

return result;
};
11 changes: 8 additions & 3 deletions packages/core/src/package-utils/dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ export const createDtsBundle = async (
const bundle = await rollup({
input: entries.map((entry) => entry.entryPath),
plugins: [
// patching imports is not needed for dts, as TypeScript can handle it (for now)
externals(config, 'cjs'),
externals(
config,
'cjs', // patching imports is not needed for dts, as TypeScript can handle it (for now)
/\.css$/, // ignore CSS files
),
dts({
respectExternal: true,
compilerOptions: config.dtsOptions as any,
Expand All @@ -41,7 +44,7 @@ export const createDtsBundle = async (
preserveEntrySignatures: 'strict',
});

await bundle.write({
const result = await bundle.write({
...commonOutputOptions(config, entries, 'dts'),
exports: 'named',
format: 'esm',
Expand All @@ -55,4 +58,6 @@ export const createDtsBundle = async (
});

await bundle.close();

return result;
};
26 changes: 22 additions & 4 deletions packages/core/src/plugins/rollup/externals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ async function findDependencies(options: ExternalsOptions) {
return packagesById;
}

export function externals(config: EnhancedConfig, format?: Format): Plugin {
export function externals(
config: EnhancedConfig,
format?: Format,
forceExternal?: RegExp,
): Plugin {
const packageRoot = config.root;
const packagePath = path.join(packageRoot, 'package.json');
// eslint-disable-next-line no-sync
Expand Down Expand Up @@ -138,12 +142,12 @@ export function externals(config: EnhancedConfig, format?: Format): Plugin {

resolveId: {
order: 'pre',
async handler(id, ...rest) {
// `resolveId` is async in Rollup 3
async handler(id, importer, hookOptions) {
const resolved = await (plugin as FunctionPluginHooks).resolveId.call(
this,
id,
...rest,
importer,
hookOptions,
);

if (
Expand All @@ -166,6 +170,20 @@ export function externals(config: EnhancedConfig, format?: Format): Plugin {

return patched;
}

if (forceExternal) {
const resolvedByRollup = await this.resolve(id, importer, {
skipSelf: true,
...hookOptions,
});
if (resolvedByRollup && forceExternal.test(resolvedByRollup.id)) {
return {
id: resolvedByRollup.id,
external: true,
};
}
}

if (!id.match(ABSOLUTE_OR_RELATIVE)) {
logDebugOnce(`Internalized dependency ${id}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,37 +98,6 @@ Snapshot Diff:
"main": "./dist/index.cjs",
`;

exports[`diffPackageJson > incorrect package.json > exports out of order > diffs 1`] = `
[
{
"key": "exports",
},
]
`;

exports[`diffPackageJson > incorrect package.json > exports out of order > package.json 1`] = `
Snapshot Diff:
- Diff A
+ Diff B

@@ --- --- @@
"exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ },
"./css": {
@@ --- --- @@
"./package.json": "./package.json",
- ".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.mjs",
- "require": "./dist/index.cjs",
- },
},
`;

exports[`diffPackageJson > incorrect package.json > files missing > diffs 1`] = `
[
{
Expand Down
Loading