Skip to content

Commit

Permalink
fix(clerk-js): Bundle CSS into CJS and ESM bundles
Browse files Browse the repository at this point in the history
  • Loading branch information
dstaley committed Oct 8, 2024
1 parent 54a836c commit 2926b6d
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 16 deletions.
22 changes: 16 additions & 6 deletions packages/clerk-js/src/ui/new/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ClerkInstanceContext, OptionsContext } from '@clerk/shared/react';
import type { ClerkHostRouter } from '@clerk/shared/router';
import { ClerkHostRouterContext } from '@clerk/shared/router';
import type { ClerkOptions, LoadedClerk } from '@clerk/types';
import stylesheetURL from '@clerk/ui/styles.css';
import stylesheetURLOrContent from '@clerk/ui/styles.css';
import type { ElementType, ReactNode } from 'react';
import { createElement, lazy } from 'react';
import { createPortal } from 'react-dom';
Expand Down Expand Up @@ -43,12 +43,22 @@ export function init({ wrapper }: { wrapper: ElementType }) {
document.body.appendChild(rootElement);

// Just for completeness, we check to see if we've already added the stylesheet to the DOM.
const STYLESHEET_SIGIL = 'data-clerk-styles';
const existingStylesheet = document.querySelector(`link[${STYLESHEET_SIGIL}]`);
const STYLESHEET_SIGIL = 'data-clerk-injected-styles';
const existingStylesheet = document.querySelector(`[${STYLESHEET_SIGIL}]`);
if (!existingStylesheet) {
const stylesheet = document.createElement('link');
stylesheet.href = stylesheetURL;
stylesheet.rel = 'stylesheet';
let stylesheet: HTMLLinkElement | HTMLStyleElement;

if (stylesheetURLOrContent.endsWith('.css')) {
// stylesheetURLOrContent is a URL to a stylesheet
stylesheet = document.createElement('link');
(stylesheet as HTMLLinkElement).href = stylesheetURLOrContent;
(stylesheet as HTMLLinkElement).rel = 'stylesheet';
} else {
// stylesheetURLOrContent is CSS
stylesheet = document.createElement('style');
stylesheet.textContent = stylesheetURLOrContent;
}

stylesheet.setAttribute(STYLESHEET_SIGIL, '');
// Add as first stylesheet so that application styles take precedence over our styles.
document.head.prepend(stylesheet);
Expand Down
69 changes: 59 additions & 10 deletions packages/clerk-js/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,22 +170,58 @@ const typescriptLoaderDev = () => {
};
};

/** @type { () => (import('webpack').RuleSetRule) } */
/**
* Used in outputs that utilize chunking, and returns a URL to the stylesheet.
* @type { () => (import('webpack').RuleSetRule) }
*/
const clerkUICSSLoader = () => {
// This emits a module exporting the URL to the styles.css file.
// This emits a module exporting a URL to the styles.css file.
return {
test: /packages\/ui\/dist\/styles\.css/,
type: 'asset/resource',
};
};

/** @type { () => (import('webpack').Configuration) } */
const commonForProd = () => {
/**
* Used in outputs that _do not_ utilize chunking, and returns the contents of the stylesheet.
* @type { () => (import('webpack').RuleSetRule) }
*/
const clerkUICSSSourceLoader = () => {
// This emits a module exporting the contents of the styles.css file.
return {
test: /packages\/ui\/dist\/styles\.css/,
type: 'asset/source',
};
};

/**
* Used for production builds that have dynamicly loaded chunks.
* @type { () => (import('webpack').Configuration) }
* */
const commonForProdChunked = () => {
return {
devtool: undefined,
module: {
rules: [svgLoader(), typescriptLoaderProd(), clerkUICSSLoader()],
},
};
};

/**
* Used for production builds that combine all files into one single file (such as for Chrome Extensions).
* @type { () => (import('webpack').Configuration) }
* */
const commonForProdBundled = () => {
return {
module: {
rules: [svgLoader(), typescriptLoaderProd(), clerkUICSSSourceLoader()],
},
};
};

/** @type { () => (import('webpack').Configuration) } */
const commonForProd = () => {
return {
devtool: undefined,
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
Expand Down Expand Up @@ -238,12 +274,18 @@ const entryForVariant = variant => {

/** @type { () => (import('webpack').Configuration)[] } */
const prodConfig = ({ mode }) => {
const clerkBrowser = merge(entryForVariant(variants.clerkBrowser), common({ mode }), commonForProd());
const clerkBrowser = merge(
entryForVariant(variants.clerkBrowser),
common({ mode }),
commonForProd(),
commonForProdChunked(),
);

const clerkHeadless = merge(
entryForVariant(variants.clerkHeadless),
common({ mode }),
commonForProd(),
commonForProdChunked(),
// Disable chunking for the headless variant, since it's meant to be used in a non-browser environment and
// attempting to load chunks causes issues due to usage of a dynamic publicPath. We generally are only concerned with
// chunking in our browser bundles.
Expand All @@ -262,10 +304,11 @@ const prodConfig = ({ mode }) => {
entryForVariant(variants.clerkHeadlessBrowser),
common({ mode }),
commonForProd(),
commonForProdChunked(),
// externalsForHeadless(),
);

const clerkEsm = merge(entryForVariant(variants.clerk), common({ mode }), commonForProd(), {
const clerkEsm = merge(entryForVariant(variants.clerk), common({ mode }), commonForProd(), commonForProdBundled(), {
experiments: {
outputModule: true,
},
Expand All @@ -283,13 +326,19 @@ const prodConfig = ({ mode }) => {
],
});

const clerkCjs = merge(clerkEsm, {
const clerkCjs = merge(entryForVariant(variants.clerk), common({ mode }), commonForProd(), commonForProdBundled(), {
output: {
filename: '[name].js',
libraryTarget: 'commonjs',
chunkFormat: 'commonjs',
scriptType: 'text/javascript',
},
plugins: [
// Include the lazy chunks in the bundle as well
// so that the final bundle can be imported and bundled again
// by a different bundler, eg the webpack instance used by react-scripts
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});

return [clerkBrowser, clerkHeadless, clerkHeadlessBrowser, clerkEsm, clerkCjs];
Expand Down

0 comments on commit 2926b6d

Please sign in to comment.