Skip to content

Commit

Permalink
Handle external CSS (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrm007 authored Jul 28, 2023
1 parent f52495d commit 433a30f
Show file tree
Hide file tree
Showing 23 changed files with 397 additions and 90 deletions.
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

0 comments on commit 433a30f

Please sign in to comment.