Skip to content
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
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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be one loader with a boolean argument for supportsChunks. I didn't have a strong opinion one way to the other, so happy to change this if anyone else has a stronger opinion!

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 = () => {

@dstaley dstaley Oct 8, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also do something like commonForProd({ chunked: true }) and do everything within the commonForProd function.

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(), {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the merge(clerkEsm portion because the experimental ESM output was confusing Webpack. It was emitting references to import.meta.url within the CJS output, which caused ATTW to complain.

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